preface
This article is written to learn how the Spring IOC container works. It is not representative of the Spring IOC container. It simply implements the container’s dependency injection and inversion of control functions.
start
Create a project
Create the Gradle project and modify build.gradle
plugins {
id 'java'
id "io.franzbecker.gradle-lombok" version "3.1.0"
}
group 'io.github.gcdd1993'
version 1.0 the SNAPSHOT ' '
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit'.name: 'junit'.version: '4.12'
}
Copy the code
createBeanFactory
BeanFactory is the IOC’s core interface for storing bean instances and getting beans. Its core methods are getBean and its overloaded methods. Here we simply implement two getBean methods.
package io.github.gcdd1993.ioc.bean;
/**
* bean factory interface
*
* @author gaochen
* @date2019/6/2 * /
public interface BeanFactory {
/** * get bean ** by bean name@paramName Bean name *@return bean
*/
Object getBean(String name);
/** * Get bean ** by bean type@paramTClass Bean type *@param<T> Generic T *@return bean
*/
<T> T getBean(Class<T> tClass);
}
Copy the code
createApplicationContext
context
The ApplicationContext, often referred to as the ApplicationContext, is actually the Spring container itself.
We create the ApplicationContext class and implement the BeanFactory interface.
public class ApplicationContext implements BeanFactory {}Copy the code
getBean
methods
Since we’re talking about containers, we must have a place to put our bean instances, using two maps as containers.
/** * Group by beanName */
private final Map<String, Object> beanByNameMap = new ConcurrentHashMap<>(256);
/** * Group by beanClass */
private finalMap<Class<? >, Object> beanByClassMap =new ConcurrentHashMap<>(256);
Copy the code
We can then complete our getBean method first.
@Override
public Object getBean(String name) {
return beanByNameMap.get(name);
}
@Override
public <T> T getBean(Class<T> tClass) {
return tClass.cast(beanByClassMap.get(tClass));
}
Copy the code
Is it easy to get the bean instance directly from the Map? Of course, in the real Spring container, it wouldn’t be that easy, but we’re going to take the complexity and understand the IOC container this time.
The constructor
Spring provides @ComponentScan to scan components under a package. For simplicity, we specify the package to scan directly in the constructor.
private final Set<String> basePackages;
/** * default constructor, which defaults to scanning the current package */
public ApplicationContext(a) {
this(new HashSet<>(Collections.singletonList(ApplicationContext.class.getPackage().getName())));
}
/** * full parameter constructor *@paramBasePackages List of scanned package names */
public ApplicationContext(Set<String> basePackages) {
this.basePackages = basePackages;
}
Copy the code
refresh
methods
The refresh process basically follows the following process
- Scans all the bands under the specified package
@Bean
Annotations (in Spring@Component
Annotation) class.
List<Class> beanClasses = PackageScanner.findClassesWithAnnotation(packageName, Bean.class);
System.out.println("scan classes with Bean annotation : " + beanClasses.toString());
for (Class beanClass : beanClasses) {
try {
createBean(beanClass);
} catch(ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { e.printStackTrace(); }}Copy the code
- Iterate over the class, getting the constructor of the class and all of its fields.
Constructor constructor = beanClass.getDeclaredConstructor();
Object object = constructor.newInstance();
Field[] fields = beanClass.getDeclaredFields();
Copy the code
-
Determine whether the field is dependency injected or normal.
-
If it is a normal field, initialize the field with the field type and try to get a Value from the @value annotation and shove it into the field.
Value value = field.getAnnotation(Value.class);
if(value ! =null) {
/ / injection
field.setAccessible(true);
// We need to do some type conversion from String to the corresponding type
field.set(object, value.value());
}
Copy the code
- If it is a dependency injected field, try from
beanByClassMap
If there is no instance of this field, you must first deinstantiate the type corresponding to this field.
Autowired autowired = field.getAnnotation(Autowired.class);
if(autowired ! =null) {
// Dependency injection
String name = autowired.name();
// Inject by name
Object diObj;
if(! name.isEmpty()) { diObj = beanByNameMap.get(name) ==null ?
createBean(name) :
beanByNameMap.get(name);
} else {
// Inject by typeClass<? > aClass = field.getType(); diObj = beanByClassMap.get(aClass) ==null ?
createBean(aClass) :
beanByClassMap.get(aClass);
}
/ / injection
field.setAccessible(true);
field.set(object, diObj);
}
Copy the code
Test our IOC container
Create the Address
@Data
@Bean
public class Address {
@Value("2222")
private String longitude;
@Value("1111")
private String latitude;
}
Copy the code
Create the Person and inject the Address
@Data
@Bean
public class Person {
@Autowired
private Address address;
@Value("gaochen")
private String name;
@Value("27")
private String age;
}
Copy the code
Create the test class ApplicationContextTest
public class ApplicationContextTest {
@Test
public void refresh(a) {
Set<String> basePackages = new HashSet<>(1);
basePackages.add("io.github.gcdd1993.ioc");
ApplicationContext ctx = new ApplicationContext(basePackages);
ctx.refresh();
Person person = ctx.getBean(Person.class);
System.out.println(person);
Object person1 = ctx.getBean("Person"); System.out.println(person1); }}Copy the code
The console will print:
scan classes with Bean annotation : [class io.github.gcdd1993.ioc.util.Address, class io.github.gcdd1993.ioc.util.Person]
scan classes with Bean annotation : [class io.github.gcdd1993.ioc.util.Address, class io.github.gcdd1993.ioc.util.Person]
Person(address=Address(longitude=2222, latitude=1111), name=gaochen, age=27)
Person(address=Address(longitude=2222, latitude=1111), name=gaochen, age=27)
Copy the code
As you can see, we successfully injected the Address instances into the Person instance and stored them in our own IOC container. In fact, the Spring container basically works like this, except that it provides a lot of convenient features for enterprise development, such as bean scope, custom methods for beans, and so on.
Access to the source code
The full source code is available from my Github repository at 👉 simple-IOC-container