If you’re going to learn, the best time to do it was ten years ago, followed by the present.

In the Spring Framework, IOC (Inversion of Control) is one of the most important core concepts. It is implemented by means of containers such as BeanFactory and ApplicationContext to manage object creation, dependency injection, life cycle and so on.

Below, we will analyze Spring’s source code to better understand the principle of IOC implementation.

BeanFactory interface

In Spring’s IOC container, BeanFactory is the most basic container interface. It defines the basic behavior of an IOC container, including operations such as getting a bean, destroying a bean, and so on.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface BeanFactory {
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    boolean containsBean(String name);
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    String[] getAliases(String name);
}

The core method in the above interface is the getBean() method, which returns the corresponding bean object by its name or type. In the Spring IOC implementation, the getBean() method is realized through the BeanFactory, let’s take a look at the default implementation class of BeanFactory: DefaultListableBeanFactory.

DefaultListableBeanFactory

DefaultListableBeanFactory is the default implementation class of Spring IOC container. It implements the BeanFactory interface and provides operations for defining, registering, and fetching beans.

In DefaultListableBeanFactory, the core is the BeanDefinition object. It is the core data structure in the Spring IOC container, representing a bean’s definition information, including the bean’s class name, constructor, properties, dependencies and so on. The following is the definition of the BeanDefinition interface.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface BeanDefinition {
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;

    String getBeanClassName();
    void setBeanClassName(String beanClassName);
    String getScope();
    void setScope(String scope);
    boolean isSingleton();
    boolean isPrototype();
    String getFactoryMethodName();
    void setFactoryMethodName(String factoryMethodName);
    String getFactoryBeanName();
    void setFactoryBeanName(String factoryBeanName);
    String[] getDependsOn();
    void setDependsOn(String... dependsOn);
    boolean isLazyInit();
    void setLazyInit(boolean lazyInit);
    ConstructorArgumentValues getConstructorArgumentValues();
    MutablePropertyValues getPropertyValues();
    boolean isAutowireCandidate();
    void setAutowireCandidate(boolean autowireCandidate);
}

The core of the above interfaces are the getBeanClassName() and getPropertyValues() methods. getBeanClassName() returns the class name of the bean and getPropertyValues() returns the property values of the bean. In the DefaultListableBeanFactory, the BeanDefinition is in the DefaultListableBeanFactory, and the BeanDefinition is stored in a Map, where the key is the name of the bean and the value is the corresponding BeanDefinition object. The specific implementation is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

    // Map storing the BeanDefinition.
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    // Registering a BeanDefinition Implementation
    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        synchronized (this.beanDefinitionMap) {
            BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
            if (existingDefinition != null) {
                if (!beanDefinition.equals(existingDefinition)) {
                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(),
                            beanName, "Definition of bean '" + beanName + "' differs from existing " +
                            "definition for the same bean name. " + "Consider deleting or renaming the existing " +
                            "bean definition with the same name.");
                }
            }
            else {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.frozenBeanDefinitionNames = null;
            }
        }
    }

    // Get the BeanDefinition implementation
    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
        Assert.hasText(beanName, "Bean name must not be empty");
        BeanDefinition bd = this.beanDefinitionMap.get(beanName);
        if (bd == null) {
            throw new NoSuchBeanDefinitionException(beanName);
        }
        return bd;
    }

    // Get the bean object based on the bean name.
    @Override
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null, null, false);
    }

    // Get the bean object according to the bean type.
    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        return doGetBean(requiredType.getName(), requiredType, null, false);
    }

    // Get bean object based on bean name and type
    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return doGetBean(name, requiredType, null, false);
    }

    // Core method, get bean object by bean name or type
    protected <T> T doGetBean(final String name, final Class<T> requiredType,
            final Object[] args, boolean typeCheckOnly) throws BeansException {

        // Parsing BeanDefinition objects by name or type
        final BeanDefinition beanDefinition = getBeanDefinition(name);

        // Creating a Bean Instance from a BeanDefinition Object
        Object bean = createBean(name, beanDefinition, args);

        // Dependency injection for bean instances
        populateBean(beanName, beanDefinition, bean);

        // Initialize the bean instance
        initializeBean(beanName, bean, beanDefinition);

        // Returns the bean instance
        return (T) bean;
    }
}

In DefaultListableBeanFactory, the operations of bean creation, dependency injection and initialization are done in the doGetBean() method. Next, we will focus on the bean creation and dependency injection process.

Bean

Bean creation process

In DefaultListableBeanFactory, the creation of bean is done through createBean() method, which is implemented as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected Object createBean(final String beanName, final BeanDefinition beanDefinition, final Object[] args) {
    // Creating a Bean Instance
    Object beanInstance = createBeanInstance(beanDefinition, beanName, args);

    // Setting the Class property of a bean instance
    Class<?> beanType = beanInstance.getClass();
    if (!beanType.equals(Object.class)) {
        beanDefinition.setBeanClass(beanType);
    }

    // Property assignment and method injection for bean instances
    applyPropertyValues(beanName, beanDefinition, beanInstance);

    // Handling callbacks for the Aware interface
    invokeAwareMethods(beanName, beanInstance);

    // Bean post-processor pre-processing
    Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(beanInstance, beanName);

    // Calling the initialization method of a bean instance
    try {
        invokeInitMethods(beanName, wrappedBean, beanDefinition);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Invocation of init method failed", ex);
    }

    // Bean post-processor post-processing
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

    // Returns the bean instance
    return wrappedBean;
}

In the createBean() method, first create the bean instance through the createBeanInstance() method, then set the Class attribute of the bean instance, assign attributes and inject methods into the bean instance, handle the callbacks of the Aware interface, call the initialization methods of the bean instance, and finally, through the bean post-processor pre-processing and post-processing, and finally return the bean instance.

Bean’s dependency injection process

In DefaultListableBeanFactory, the dependency injection of the bean is done through the applyPropertyValues() method, which is implemented as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
protected void applyPropertyValues(String beanName, BeanDefinition beanDefinition, Object bean) {
    try {
        // Property assignment and method injection for bean instances
        PropertyValues pvs = beanDefinition.getPropertyValues();
        if (pvs != null) {
            for (PropertyValue pv : pvs.getPropertyValues()) {
                String propertyName = pv.getName();
                Object originalValue = pv.getValue();
                Object resolvedValue = resolveValueIfNecessary(beanName, beanDefinition, propertyName, originalValue);
                BeanWrapper bw = new BeanWrapperImpl(bean);
                bw.setPropertyValue(propertyName, resolvedValue);
            }
        }
    }
    catch (BeansException ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
}

In the applyPropertyValues() method, first get the PropertyValues property of the BeanDefinition object, then iterate through the PropertyValue objects in it to get the property name and property value. And resolve the property value through resolveValueIfNecessary() method. Finally, the BeanWrapperImpl is used to assign properties to the bean instance.

During the property assignment process, if the property value is a reference type, it will try to do autowiring. The implementation is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
protected Object resolveValueIfNecessary(String beanName, BeanDefinition beanDefinition, String propertyName,
        Object originalValue) {
    if (originalValue instanceof BeanDefinitionHolder) {
        // If the value of the property is a BeanDefinitionHolder object, it means that the property is a reference type
        BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) originalValue;
        Object ref = resolveReference(bdHolder, beanName, propertyName);
        if (ref instanceof NullBean) {
            return null;
        }
        return ref;
    }
    else if (originalValue instanceof RuntimeBeanReference) {
        // If the property value is a RuntimeBeanReference object, it means that the property is a reference type
        RuntimeBeanReference ref = (RuntimeBeanReference) originalValue;
        return resolveReference(ref, beanName, propertyName);
    }
    else if (originalValue instanceof ObjectFactory) {
        // If the value of the property is an ObjectFactory object, it means that the property is a lazy-loaded type
        ObjectFactory<?> objectFactory = (ObjectFactory<?>) originalValue;
        return objectFactory.getObject();
    }
    else if (originalValue instanceof ObjectProvider) {
        // If the property value is an ObjectProvider object, it means that the property is a lazy-loaded type
        ObjectProvider<?> objectProvider = (ObjectProvider<?>) originalValue;
        return objectProvider.getObject();
    }
    else {
        // If the property value is not a reference type, the original value is returned.
        return originalValue;
    }
}

protected Object resolveReference(Object ref, String beanName, String propertyName) {
    if (ref instanceof RuntimeBeanReference) {
        // If the reference type is a RuntimeBeanReference object, this indicates that autowiring is required.
        RuntimeBeanReference reference = (RuntimeBeanReference) ref;
        String refName = reference.getBeanName();
        Object bean;
        try {
            bean = getBean(refName);
        }
        catch (BeansException ex) {
            throw new BeanCreationException(beanName, "Could not resolve reference to bean '" + refName + "' in property '" + propertyName + "'", ex);
        }
        if (bean instanceof NullBean) {
            return null;
        }
        return bean;
    }
    else if (ref instanceof BeanDefinitionHolder) {
        // If the reference type is a BeanDefinitionHolder object, this indicates that autowiring is required.
        BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) ref;
        return resolveReference(bdHolder.getBeanDefinition(), beanName, propertyName);
    }
    else {
        // If the reference type is not a RuntimeBeanReference or BeanDefinitionHolder object, return the reference type directly.
        return ref;
    }
}

In the resolveValueIfNecessary() method, if the value of the property is a BeanDefinitionHolder object or a RuntimeBeanReference object, it means that the property is a reference type and needs to be autowired. At this point, the resolveReference() method is called for reference resolution and autowiring.

In the resolveReference() method, if the reference type is a RuntimeBeanReference object or a BeanDefinitionHolder object, it means that automatic wiring is required. In this case, the getBean() method will get the bean instance corresponding to the reference type and return the instance. If the bean instance cannot be obtained, an exception will be thrown.

During autowiring, Spring will first match the attributes based on their type, and if it finds a unique bean instance, it will use that instance for autowiring. If a unique bean instance is still not found, an exception is thrown.

In addition to autowiring, Spring also supports manual wiring, where the value of an attribute is explicitly specified through a configuration element. Example:

1
2
3
<bean id="foo" class="com.example.Foo">
    <property name="bar" value="hello"/>
</bean>

The above configuration represents the creation of a bean instance named foo, which has a class property of com.example.Foo and a bar property with a value of the string hello.

During manual wiring, Spring parses the configured property value to a value of type String, converts it to the target property type, and finally sets the converted value to the target property.

The above is the basic content of Spring IOC source code analysis, by reading the source code, we can deeply understand the principle of Spring IOC implementation, for the use and customization of Spring provides a good foundation.

Ref

https://mp.weixin.qq.com/s/UuFdWGjBmJlDg4BelGL8GQ