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

createApplicationContextcontext

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

getBeanmethods

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

refreshmethods

The refresh process basically follows the following process

  1. Scans all the bands under the specified package@BeanAnnotations (in Spring@ComponentAnnotation) 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
  1. 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
  1. Determine whether the field is dependency injected or normal.

  2. 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
  1. If it is a dependency injected field, try frombeanByClassMapIf 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