Preface
This article is written to learn the execution process of the Spring IOC container. It can't fully represent the Spring IOC container, but simply implements the dependency injection and control inversion functions of the container, which can't be used in production. It can only play a certain role in understanding the Spring container.
start
Create 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-SNAPSHOT' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' }
Create BeanFactory
BeanFactory is the core interface used to store bean instances and get beans in IOC. Its core methods are getBean and overload methods of getBean. Here, two methods of getBean are simply implemented.
package io.github.gcdd1993.ioc.bean; /** * bean factory interface * * @author gaochen * @date 2019/6/2 */ public interface BeanFactory { /** * Get bean by bean name * * @param name bean Name * @return bean */ Object getBean(String name); /** * Get bean by bean type * * @param tClass bean type * @param <T> Generic T * @return bean */ <T> T getBean(Class<T> tClass); }
Create ApplicationContext context
ApplicationContext, as we often say, is actually the Spring container itself.
We create the ApplicationContext class and implement the BeanFactory interface.
public class ApplicationContext implements BeanFactory { }
getBean method
Since it's a container, there must be a place to hold our bean instance. Use two maps as containers.
/** * Group by beanName */ private final Map<String, Object> beanByNameMap = new ConcurrentHashMap<>(256); /** * Group by bean class */ private final Map<Class<?>, Object> beanByClassMap = new ConcurrentHashMap<>(256);
Then, we can finish 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)); }
Is it easy to get bean instances directly from the Map? Of course, in a real Spring container, it won't be so simple, but this time we want to simplify the complexity and understand the IOC container.
constructor
Spring provides @ ComponentScan to scan the components under the package. For convenience, we directly specify the package to scan in the constructor.
private final Set<String> basePackages; /** * Default constructor, scan the current package by default */ public ApplicationContext() { this(new HashSet<>(Collections.singletonList(ApplicationContext.class.getPackage().getName()))); } /** * All parameter constructor * @param basePackages List of scanned package names */ public ApplicationContext(Set<String> basePackages) { this.basePackages = basePackages; }
refresh method
The process of refresh basically follows the following process
- Scan all classes with @ Bean annotation (in Spring, @ Component annotation) under the specified package.
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(); } }
- Traverse the class, get the constructor of the class and all fields.
Constructor constructor = beanClass.getDeclaredConstructor(); Object object = constructor.newInstance(); Field[] fields = beanClass.getDeclaredFields();
Determine whether the field is dependent on the injected or normal field.
If it is a normal field, initialize the field through the field type and try to get the Value from the @ Value annotation and plug it into the field.
Value value = field.getAnnotation(Value.class); if (value != null) { // injection field.setAccessible(true); // Some type conversion is needed, from String to corresponding type field.set(object, value.value()); }
- If it is a dependency injected field, try to get the corresponding instance from beanByClassMap. If not, first instantiate the corresponding type of the 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 type Class<?> aClass = field.getType(); diObj = beanByClassMap.get(aClass) == null ? createBean(aClass) : beanByClassMap.get(aClass); } // injection field.setAccessible(true); field.set(object, diObj); }
Test our IOC container
Create Address
@Data @Bean public class Address { @Value("2222") private String longitude; @Value("1111") private String latitude; }
Create Person and inject Address
@Data @Bean public class Person { @Autowired private Address address; @Value("gaochen") private String name; @Value("27") private String age; }
Create test class ApplicationContextTest
public class ApplicationContextTest { @Test public void refresh() { 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); } }
The console will output:
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)
As you can see, we successfully injected the Address instance into the Person instance and stored them in our own IOC container. In fact, the principle of Spring container is basically the same. In order to cope with enterprise level development, it provides many convenient functions, such as bean scope, bean custom methods and so on.
Get source code
The full source code can be obtained from my github repository Simple-IOC-Container