Spring Bean loading process

1 Overview

As an Ioc framework, Spring implements dependency injection, and a centralized Bean factory is responsible for the instantiation and dependency management of each Bean. Each Bean does not need to care about its own complex creation process, achieving a good decoupling effect. Let's make a rough summary of Spring's workflow, which mainly consists of two major links:

  • Parsing : Read xml configuration, scan class files, obtain Bean definition information from configuration or annotations, and register some extended functions.
  • Loading : Obtain the Bean instance through the parsed definition information.

java spring bean loading spring loading bean process_java spring bean loading

Spring overall process

We assume that all configuration and extension classes have been loaded into the ApplicationContext, and then analyze the Bean loading process in detail. Think about a question, put aside the implementation of the Spring framework, assume that we already have a complete set of Bean Definition Map on hand, and then specify a beanName to be instantiated, what do we need to care about? Even if we don't have the Spring framework, we still need to understand these two aspects:

  • Scope : Singleton scope or prototype scope. Singleton needs to be instantiated globally once, and prototype needs to be re-instantiated every time it is created.
  • Dependencies : If a Bean has dependencies, we need to initialize the dependencies and then associate them. If there is a circular dependency between multiple beans, A depends on B, B depends on C, and C depends on A, this circular dependency problem needs to be solved.

Spring abstracts and encapsulates, making the configuration of scope and dependencies transparent to developers. We only need to know that its life cycle and who it depends on have been clearly specified in the configuration. As for how it is implemented, how dependencies are injected , entrusted to the Spring factory for management. Spring only exposes a very simple interface to callers, such as getBean:

ApplicationContext context = new ClassPathXmlApplicationContext("hello.xml");
HelloBean helloBean = (HelloBean) context.getBean("hello");
helloBean.sayHello();1.2.3.

Then we will use getBeanthe method as the entry point to understand what the Spring loading process is like, as well as the internal processing details of creation information, scope, dependencies, etc.

2. Overall process

java spring bean loading spring loading bean process_parent class_02

Bean loading flowchart

The above is a flowchart that tracks the creation of the call chain of getBean. In order to better understand the Bean loading process, some exceptions, logs and branch processing and the judgment of some special conditions are omitted. From the flow chart above, you can see that a Bean loading will go through the following stages (marked in green):

  • Get BeanName : parse the incoming name and convert it into a bean name that can obtain the BeanDefinition from the Map.
  • Merge Bean definition : merge and overwrite the definition of the parent class. If the parent class has a parent class, a recursive merge will be performed to obtain the complete Bean definition information.
  • Instantiation : Create a Bean instance using a constructor or factory method.
  • Attribute filling : Find and inject dependencies, and dependent beans will also call getBeanmethods to obtain recursively.
  • Initialization : Call the custom initialization method.
  • Get the final Bean : If it is FactoryBean, you need to call the getObject method. If type conversion is required, call TypeConverter for conversion.

The most complex part of the entire process is the solution to circular dependencies, which will be analyzed in detail later.

3. Detailed Analysis

3.1. Convert BeanName

The Map created after we parse the configuration uses beanName as the key. See DefaultListableBeanFactory:

/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);1.2.

BeanFactory.getBeanThe name passed in may be the following situations:

  • bean name : You can directly obtain the definition BeanDefinition.
  • alias name : alias, needs to be converted.
  • factorybean name : with & prefix , you need to remove the & prefix when obtaining the BeanDefinition through it.

In order to obtain the correct BeanDefinition, you need to convert the name first to get the beanName.

java spring bean loading spring loading bean process_java spring bean loading_03

name to beanName

See AbstractBeanFactory.doGetBean:

protected <T> T doGetBean ... {
    
    
    ...
    
    // 转化工作 
    final String beanName = transformedBeanName(name);
    ...
}

If it is an alias name , during the parsing phase, the mapping relationship between the alias name and the bean name is registered in SimpleAliasRegistry. Get the beanName from the register. See SimpleAliasRegistry.canonicalName:

public String canonicalName(String name) {
    
    
    ...
    resolvedName = this.aliasMap.get(canonicalName);
    ...
}

If it is factorybean name , it means that this is a factory bean. If it carries a prefix modifier &, just remove the prefix. See BeanFactoryUtils.transformedBeanName:

public static String transformedBeanName(String name) {
    
    
    Assert.notNull(name, "'name' must not be null");
    String beanName = name;
    while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
    
    
        beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
    }
    return beanName;
}

3.2. Merge RootBeanDefinition

The BeanDefinition we read from the configuration file is GenericBeanDefinition . It records some properties or construction parameters declared by the current class, but only uses one for the parent class parentName.

public class GenericBeanDefinition extends AbstractBeanDefinition {
    
    
    ...
    private String parentName;
    ...
}

Next, you will find a problem. When instantiating the Bean later, the BeanDefinition used is the RootBeanDefinition type instead of the GenericBeanDefinition . why is that? The answer is obvious. GenericBeanDefinition does not have enough definition information when there is an inheritance relationship:

  • If there is no inheritance relationship, the information stored in GenericBeanDefinition is complete and can be directly converted into RootBeanDefinition.
  • If there is an inheritance relationship, GenericBeanDefinition stores incremental information rather than full information .

In order to initialize the object correctly, complete information is required . Need to recursively merge the definition of the parent class :

java spring bean loading spring loading bean process_parent class_04

Merge BeanDefinition

See AbstractBeanFactory.doGetBean:

protected <T> T doGetBean ... {
    
    
    ...
    
    // 合并父类定义
    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        
    ...
        
    // 使用合并后的定义进行实例化
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        
    ...
}

If it is determined parentNamethat exists, it means that the parent class definition exists and the merge is started. What if the parent class still has a parent class? Recursive call to continue merging. See AbstractBeanFactory.getMergedBeanDefinitionmethod:

protected RootBeanDefinition getMergedBeanDefinition(
            String beanName, BeanDefinition bd, BeanDefinition containingBd)
            throws BeanDefinitionStoreException {
    
    

        ...
        
        String parentBeanName = transformedBeanName(bd.getParentName());

        ...
        
        // 递归调用,继续合并父类定义
        pbd = getMergedBeanDefinition(parentBeanName);
        
        ...

        // 使用合并后的完整定义,创建 RootBeanDefinition
        mbd = new RootBeanDefinition(pbd);
        
        // 使用当前定义,对 RootBeanDefinition 进行覆盖
        mbd.overrideFrom(bd);

        ...
        return mbd;
    
    }

Each time the parent class definition is merged, it will be called to RootBeanDefinition.overrideFromoverwrite the parent class definition and obtain all the information that the current class can correctly instantiate .

3.3. Handling circular dependencies

What is a circular dependency? For example, there are three classes A, B, and C. Then A is related to B, B is related to C, and C is related to A. This forms a circular dependency. If it is a method call, it is not considered a circular dependency. The circular dependency must hold a reference.

java spring bean loading spring loading bean process_loading_05

circular dependency

Circular dependencies are divided into two types according to the timing of injection:

  • Constructor circular dependency : The dependent object is passed in through the constructor, which occurs when the Bean is instantiated. This dependency is inherently irresolvable . For example, we accurately call the constructor of A and find that it depends on B, so we call the constructor of B for instantiation. We find that it depends on C, so we call the constructor of C to initialize, and the result is that it depends on A. The whole thing forms a deadlock, causing A to be unable to create.
  • Set value circular dependency : The dependent object is passed in through the setter method, the object has been instantiated, and property filling and dependency injection occur. The Spring framework only supports setting circular dependencies under singletons . Spring caches and exposes the singleton in advance by caching the singleton while it is still being created, so that other instances can reference the dependency.

3.3.1. Circular dependency of prototype pattern

Spring does not support any circular dependencies in the prototype pattern . If a circular dependency is detected, a BeanCurrentlyInCreationException exception will be thrown directly. A ThreadLocal variable prototypesCurrentlyInCreation is used to record the Bean object being created by the current thread, see AbtractBeanFactory#prototypesCurrentlyInCreation:

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
            new NamedThreadLocal<Object>("Prototype beans currently in creation");1.2.3.

Logging is done before the bean is created and is deleted after the bean is created. See AbstractBeanFactory.doGetBean:

...
if (mbd.isPrototype()) {
    
    
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
    
    
    
        // 添加记录
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
    
    
        // 删除记录
        afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
...

See AbtractBeanFactory.beforePrototypeCreationthe logging operations for:

protected void beforePrototypeCreation(String beanName) {
    
    
        Object curVal = this.prototypesCurrentlyInCreation.get();
        if (curVal == null) {
    
    
            this.prototypesCurrentlyInCreation.set(beanName);
        }
        else if (curVal instanceof String) {
    
    
            Set<String> beanNameSet = new HashSet<String>(2);
            beanNameSet.add((String) curVal);
            beanNameSet.add(beanName);
            this.prototypesCurrentlyInCreation.set(beanNameSet);
        }
        else {
    
    
            Set<String> beanNameSet = (Set<String>) curVal;
            beanNameSet.add(beanName);
        }
    }

See AbtractBeanFactory.beforePrototypeCreationthe delete operation for :

protected void afterPrototypeCreation(String beanName) {
    
    
        Object curVal = this.prototypesCurrentlyInCreation.get();
        if (curVal instanceof String) {
    
    
            this.prototypesCurrentlyInCreation.remove();
        }
        else if (curVal instanceof Set) {
    
    
            Set<String> beanNameSet = (Set<String>) curVal;
            beanNameSet.remove(beanName);
            if (beanNameSet.isEmpty()) {
    
    
                this.prototypesCurrentlyInCreation.remove();
            }
        }
    }

In order to save memory space, prototypesCurrentlyInCreation only records the String object for a single element , and uses the Set collection instead for multiple dependent elements. Here is a little memory saving trick used by Spring. After understanding the process of writing and deleting records, let's take a look at the way of reading and judging the loop. There are two situations to be discussed here.

  • Constructor circular dependency.
  • Set up circular dependencies.

The implementation in these two places is slightly different. If it is constructor dependent, for example, the constructor of A depends on B, there will be such a situation. In the stage of instantiating A, the constructor to be used is matched, and it is found that the constructor has parameter B, and will be used BeanDefinitionValueResolverto retrieve the instance of B. See BeanDefinitionValueResolver.resolveReference:

private Object resolveReference(Object argName, RuntimeBeanReference ref) {
    
    

    ...
    Object bean = this.beanFactory.getBean(refName);
    ...
}

We find that we continue to call here beanFactory.getBeanto load B. If it is a circular dependency, for example, we do not provide a constructor here and use @Autowire to annotate the dependency (there are other ways not to give examples):

public class A {
    
    
    @Autowired
    private B b;
    ...
}

During the loading process, the parameterless constructor is found, there is no need to retrieve the references of the construction parameters, and the instantiation is successful. Then execute and enter the attribute filling stage AbtractBeanFactory.populateBean, where B's dependency injection will be performed. In order to obtain the instantiated reference of B, DependencyDescriptorthe dependencies will be read out through the search class, see DependencyDescriptor.resolveCandidate:

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
            throws BeansException {
    
    
    return beanFactory.getBean(beanName, requiredType);
}

The discovery beanFactory.getBeanmethod is called again. Here, the two circular dependencies are identical . Whether it is a circular dependency of the constructor or a circular dependency setting, when a dependent object needs to be injected, it will continue to be called beanFactory.getBeanto load the object, forming a recursive operation. Before and after instantiation of each call beanFactory.getBean, the variable prototypesCurrentlyInCreation is used for recording. Following the idea here, the overall effect is equivalent to establishing a construction chain of dependent objects . The values ​​in prototypesCurrentlyInCreation change as follows:

java spring bean loading spring loading bean process_instantiation_06

Prototype pattern circular dependencies

The place where the decision is called is in AbstractBeanFactory.doGetBean, and the instantiation of all objects starts from here.

// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
    
    
    throw new BeanCurrentlyInCreationException(beanName);
}

The implementation method of determination is AbstractBeanFactory.isPrototypeCurrentlyInCreation:

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    
    
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null &&
        (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

Therefore, in the prototype mode, the constructor circular dependency and the set value circular dependency are essentially detected in the same way. Spring can't solve it, and throws BeanCurrentlyInCreationException directly.

3.3.2. Singleton pattern construction circular dependency

Spring also does not support circular dependencies in the construction of the singleton pattern . A BeanCurrentlyInCreationException is also thrown when a construction circular dependency is detected. Similar to the prototype pattern, the singleton pattern also uses a data structure to record the beanName being created. See DefaultSingletonBeanRegistry:

/** Names of beans that are currently in creation */
private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));1.2.3.

It will be recorded before creation, and will be deleted after creation. SeeDefaultSingletonBeanRegistry.getSingleton

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    
    
        ...
        
        // 记录正在加载中的 beanName
        beforeSingletonCreation(beanName);
        ...
        // 通过 singletonFactory 创建 bean
        singletonObject = singletonFactory.getObject();
        ...
        // 删除正在加载中的 beanName
        afterSingletonCreation(beanName);
        
}

For methods of recording and judgment, see DefaultSingletonBeanRegistry.beforeSingletonCreation:

protected void beforeSingletonCreation(String beanName) {
    
    
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    
    
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }

Here we will try to record the currently instantiated bean to singletonsCurrentlyInCreation . We know that the data structure of singletonsCurrentlyInCreation is Set, which does not allow repeated elements, so once recorded previously, the add operation here will return failure . For example, loading the singleton of A is similar to the prototype mode. The singleton mode will also call the matching constructor to be used. It is found that the constructor has parameter B, and then uses to BeanDefinitionValueResolverretrieve the instance of B. Based on the above analysis, continue to call beanFactory.getBeanthe method. So take the examples of A, B, and C as an example of the changes in singletonsCurrentlyInCreation . Here you can see that the algorithm is the same as the circular dependency judgment method of prototype mode:

java spring bean loading spring loading bean process_java spring bean loading_07

Singleton pattern construction circular dependency

  • Load A. Record singletonsCurrentlyInCreation = [a] , construct dependency B, and start loading B.
  • Load B, record singletonsCurrentlyInCreation = [a, b] , construct dependency C, and start loading C.
  • Load C, record singletonsCurrentlyInCreation = [a, b, c] , construct dependency A, and start loading A again.
  • Load A, execute it DefaultSingletonBeanRegistry.beforeSingletonCreation, a already exists in singletonsCurrentlyInCreation, detect the construction circular dependency, and directly throw an exception to end the operation.

3.3.3. Circular dependency of setting values ​​in singleton mode

In singleton mode, the circular dependency of the constructor cannot be solved, but the circular dependency of the set value can be solved . There is an important design here: expose the singleton being created in advance . Let’s understand why we do this. Let’s analyze the value dependencies of A, B, and C above.

  • => 1. A is created -> A is constructed, starts injecting properties, discovers dependency on B, and starts instantiation of B
  • => 2. B is created -> B is constructed, starts injecting properties, finds dependency on C, starts instantiation of C
  • => 3. C is created -> C construction is completed, attributes are injected, and dependency A is found

Here’s the key point. In our phase 1, A has been constructed, and the Bean object has allocated memory in the heap. Even if attributes are subsequently filled in A (such as filling in dependent B objects), A will not be modified. Reference address. Therefore, at this time, can we get the reference of the A instance in advance and inject it into C first to complete the instantiation of C, so the process becomes like this.

  • => 3. C is created -> C is constructed, starts injecting dependencies, finds dependency A, finds that A has been constructed, directly references, and completes the instantiation of C.
  • => 4. After C completes instantiation, B injects C and completes instantiation, and A injects B and completes instantiation.

This is Spring's technique for solving circular dependency applications in singleton mode. The flow chart is:

java spring bean loading spring loading bean process_loading_08

Singleton mode creation process

In order to achieve early exposure of singletons. Spring uses three levels of caching, see DefaultSingletonBeanRegistry:

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);1.2.3.4.5.6.7.8.

The differences between these three caches are as follows:

  • singletonObjects , singleton cache, stores singletons that have been instantiated.
  • singletonFactories , the cache of the factory that produces singletons, and the storage factory.
  • earlySingletonObjects , singleton cache exposed in advance. At this time, the singleton has just been created, but dependencies will be injected.

Starting from getBean("a"), the specific implementation of the added SingletonFactory is as follows:

protected Object doCreateBean ...  {
    
    

    ...
    addSingletonFactory(beanName, new ObjectFactory<Object>() {
    
    
        @Override
        public Object getObject() throws BeansException {
    
    
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
    ...
}

It can be seen that if the SingletonFactory is used to obtain an instance, the method is used getEarlyBeanReferenceto return an uninitialized reference. See where to read the cache DefaultSingletonBeanRegistry:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    
    
        synchronized (this.singletonObjects) {
    
    
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
    
    
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
    
    
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

First try to read from singletonObjectsand singletonFactory, there is no data, then try singletonFactoriesto read singletonFactory, execute getEarlyBeanReferenceafter getting the reference, store it in earlySingletonObjects. The advantage of this earlySingletonObjectsis that if there are other places trying to get the uninitialized singleton at this time, you can earlySingletonObjectstake it out directly without calling it again getEarlyBeanReference. From the flow chart, it can be seen that the A instance injected into C is still in the attribute filling stage and has not been completely initialized. When the recursion goes back and A successfully obtains dependency B, the loading of A will be truly completed.

3.4. Creating instances

After obtaining the complete RootBeanDefintion, you can use this definition information to instantiate a specific Bean. For specific instance creation AbstractAutowireCapableBeanFactory.createBeanInstance, see BeanWrapper, the packaging class that returns Bean. There are three strategies:

  • Created using factory methods , instantiateUsingFactoryMethod.
  • Created using a parameterized constructor , autowireConstructor.
  • Created using a no-argument constructor , instantiateBean.

To create using the factory method, you will first use getBean to obtain the factory class, then find the matching factory method through the parameters, and call the instantiation method to implement instantiation. For details, see ConstructorResolver.instantiateUsingFactoryMethod:

public BeanWrapper instantiateUsingFactoryMethod ... (
    ...
    String factoryBeanName = mbd.getFactoryBeanName();
    ...
    factoryBean = this.beanFactory.getBean(factoryBeanName);
    ...
    // 匹配正确的工厂方法
    ...
    beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(...);
    ...
    bw.setBeanInstance(beanInstance);
    return bw;
}

Using a parameterized constructor to create, the whole process is more complicated, involving the matching of parameters and constructors. Spring does a lot of work to find the matching constructor, see ConstructorResolver.autowireConstructor:

public BeanWrapper autowireConstructor ... {
    
    
    ...
    Constructor<?> constructorToUse = null;
    ...
    // 匹配构造函数的过程
    ...
    beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(...);
    ...
    bw.setBeanInstance(beanInstance);
    return bw;
}

Creating using a no-argument constructor is the simplest way, see AbstractAutowireCapableBeanFactory.instantiateBean:

protected BeanWrapper instantiateBean ... {
    
    
    ...
    beanInstance = getInstantiationStrategy().instantiate(...);
    ...
    BeanWrapper bw = new BeanWrapperImpl(beanInstance);
    initBeanWrapper(bw);
    return bw;
    ...
}

We found that these three instantiation methods will all be used in the end getInstantiationStrategy().instantiate(...), see the implementation class SimpleInstantiationStrategy.instantiate:

public Object instantiate ... {
    
    
    if (bd.getMethodOverrides().isEmpty()) {
    
    
        ...
        return BeanUtils.instantiateClass(constructorToUse);
    }
    else {
    
    
        // Must generate CGLIB subclass.
        return instantiateWithMethodInjection(bd, beanName, owner);
    }
}

Although the constructor is obtained, it is not instantiated immediately. Because the user uses the replace and lookup configuration methods, a dynamic proxy is used to add the corresponding logic. If not, use reflection directly to create the instance. Once the instance is created, you can start injecting properties and initializing it. But the Bean here is not the final Bean. When returning to the caller for use, if it is a FactoryBean, you need to use the getObject method to create an instance. See AbstractBeanFactory.getObjectFromBeanInstance, it will be executed to doGetObjectFromFactoryBean:

private Object doGetObjectFromFactoryBean ... {
    
    
    ...
    object = factory.getObject();
    ...
    return object;
}

3.5. Injecting properties

After the instance is created, attribute injection begins. If an externally dependent instance is involved, it will be automatically retrieved and associated with the current instance. Ioc thinking is reflected. It is with this step that Spring reduces the coupling between various classes. The entry method for attribute filling is AbstractAutowireCapableBeanFactory.populateBean.

protected void populateBean ... {
    
    
    PropertyValues pvs = mbd.getPropertyValues();
    
    ...
    // InstantiationAwareBeanPostProcessor 前处理
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
    
    
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
    
    
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
    
    
                continueWithPropertyPopulation = false;
                break;
            }
        }
    }
    ...
    
    // 根据名称注入
    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
    
    
        autowireByName(beanName, mbd, bw, newPvs);
    }

    // 根据类型注入
    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
    
    
        autowireByType(beanName, mbd, bw, newPvs);
    }

    ... 
    // InstantiationAwareBeanPostProcessor 后处理
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
    
    
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
    
    
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
            if (pvs == null) {
    
    
                return;
            }
        }
    }
    
    ...
    
    // 应用属性值
    applyPropertyValues(beanName, mbd, bw, pvs);
}

You can see that the main processing links are:

  • Apply the InstantiationAwareBeanPostProcessor processor to process properties before and after injection. Assuming we use the @Autowire annotation, AutowiredAnnotationBeanPostProcessor will be called here to retrieve and inject dependent instances , which is a subclass of InstantiationAwareBeanPostProcessor.
  • Perform automatic injection based on name or type and store the results in PropertyValues.
  • Apply PropertyValues ​​and populate it into BeanWrapper. Here, when retrieving the reference of the dependent instance, it will be recursively called BeanFactory.getBeanto obtain it.

3.6. Initialization

3.6.1. Trigger Aware

What if our bean requires some resources from the container? For example, you need to obtain BeanFactory, ApplicationContext, etc. Spring provides the Aware series of interfaces to solve this problem. For example, there is awareness like this:

  • BeanFactoryAware, used to obtain BeanFactory.
  • ApplicationContextAware, used to obtain ApplicationContext.
  • ResourceLoaderAware, used to obtain ResourceLoaderAware.
  • ServletContextAware, used to obtain ServletContext.

During the initialization phase, Spring will inject the resources it cares into the Bean if it determines that the Bean implements one of these interfaces. See AbstractAutowireCapableBeanFactory.invokeAwareMethos:

private void invokeAwareMethods(final String beanName, final Object bean) {
    
    
    if (bean instanceof Aware) {
    
    
        if (bean instanceof BeanNameAware) {
    
    
            ((BeanNameAware) bean).setBeanName(beanName);
        }
        if (bean instanceof BeanClassLoaderAware) {
    
    
            ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
        }
        if (bean instanceof BeanFactoryAware) {
    
    
            ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
    }
}

3.6.2. Trigger BeanPostProcessor

What if we need to perform some enhancement operations before or after the initialization of the Bean? These enhanced operations include logging, verification, attribute modification, time-consuming detection, etc. The Spring framework provides BeanPostProcessor to achieve this goal. For example, we use the annotation @Autowire to declare dependencies, and use AutowiredAnnotationBeanPostProcessorto implement dependency query and injection. The interface is defined as follows:

public interface BeanPostProcessor {
    
    

    // 初始化前调用
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    // 初始化后调用
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

Beans that implement this interface will be registered by Spring in beanPostProcessors , see AbstractBeanFactory:

/** BeanPostProcessors to apply in createBean */
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();1.2.

As long as the beans implement the BeanPostProcessor interface, these beans will be automatically recognized and automatically registered by Spring when loading, which is very convenient. Then before and after the bean is instantiated, Spring will call the beanPostProcessors we have registered to execute the processors.

public abstract class AbstractAutowireCapableBeanFactory ... {
    
    
        
    ...
    
    @Override
    public Object applyBeanPostProcessorsBeforeInitialization ... {
    
    

        Object result = existingBean;
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
    
    
            result = beanProcessor.postProcessBeforeInitialization(result, beanName);
            if (result == null) {
    
    
                return result;
            }
        }
        return result;
    }

    @Override
    public Object applyBeanPostProcessorsAfterInitialization ... {
    
    

        Object result = existingBean;
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
    
    
            result = beanProcessor.postProcessAfterInitialization(result, beanName);
            if (result == null) {
    
    
                return result;
            }
        }
        return result;
    }
    
    ...
}

The chain of responsibility pattern is used here, and the beans are passed and processed in the processor chain. When we call BeanFactory.getBean, executing the Bean's initialization method AbstractAutowireCapableBeanFactory.initializeBeanwill start these processors.

protected Object initializeBean ... {
    
       
    ...
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    ...
    // 触发自定义 init 方法
    invokeInitMethods(beanName, wrappedBean, mbd);
    ...
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    ...
}

3.6.3. Trigger custom init

There are two ways to choose from for custom initialization:

  • Implement InitializingBean. Provides a good opportunity to add your own initialization logic after the property settings are completed.
  • Define init method. Custom initialization logic.

See AbstractAutowireCapableBeanFactory.invokeInitMethods:

protected void invokeInitMethods ... {
    
    

        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
    
    
            ...
            
            ((InitializingBean) bean).afterPropertiesSet();
            ...
        }

        if (mbd != null) {
    
    
            String initMethodName = mbd.getInitMethodName();
            if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
    
    
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }

3.7. Type conversion

The bean has been loaded, the properties have been populated, and the initialization is complete. There is also an opportunity to perform type conversion on the Bean instance before returning it to the caller. See AbstractBeanFactory.doGetBean:

protected <T> T doGetBean ... {
    
    
    ...
    if (requiredType != null && bean != null && !requiredType.isInstance(bean)) {
    
    
        ...
        return getTypeConverter().convertIfNecessary(bean, requiredType);
        ...
    }
    return (T) bean;
}

4. Summary

Putting aside some detailed processing and extended functions, the creation process of a Bean is nothing more than: obtaining the complete definition -> instantiation -> dependency injection -> initialization -> type conversion.

As a complete framework, Spring needs to consider various possibilities, and also needs to consider the scalability of access. Therefore, there are complex circular dependency solutions, complex parameter constructor matching processes, and BeanPostProcessor to extend and modify instantiated or initialized beans.

First have an overall design thinking, and then gradually break down the design for these special scenarios, and the entire Bean loading process can be solved easily.

Guess you like

Origin blog.csdn.net/qq_43842093/article/details/132783954