Analyze with questions: Spring Bean life cycle | JD Logistics Technology Team

1: How Beans are stored and defined in the Spring container

The definition of Bean in Spring is the org.springframework.beans.factory.config.BeanDefinition interface. BeanDefinition stores the metadata of the Java classes we wrote in Spring, including the following main metadata information:

1: Scope (Bean type): includes single instance Bean (Singleton) and multi-instance Bean (Prototype)

2: BeanClass : Bean’s Class type

3: LazyInit : Whether the Bean needs to be lazy loaded

4: AutowireMode : automatic injection type

5: DependsOn : The names of other beans that the bean depends on. Spring will initialize the dependent beans first.

6: PropertyValues : Bean’s member variable property values

7: InitMethodName : Bean’s initialization method name

8: DestroyMethodName : Bean’s destruction method name

At the same time, BeanDefinition is stored in the BeanDefinitionMap maintained in the org.springframework.beans.factory.support.DefaultListableBeanFactory class . The source code is as follows:

Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

After understanding the basic information and storage location of BeanDefinition , let's take a look at where the created Bean instance is stored. The created Bean is stored in: _ org.springframework.beans.factory.support.DefaultSingletonBeanRegistry _ class of

In _ singletonObjects _, Key is the name of the Bean, and Value is the created Bean instance:

Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

After understanding the basic information, you can analyze the life cycle of Spring Bean with the following two key questions :

1: How are Java classes scanned by Spring and turned into BeanDefinitions?

2: How is BeanDefinition processed and created by Spring into a Bean instance that we can use directly?

2: How are Java classes scanned into BeanDefinitions by Spring?

There are many ways to define beans in Spring, such as using XML files and annotations, including: @Component , @Service , @Configuration, etc. Let’s take the @Component annotation as an example to explore how Spring scans our beans. We know that using the @Component annotation to mark beans needs to be used in conjunction with the @ComponentScan annotation, and the @SpringBootApplication annotation marked on our main startup class inherits the @ComponentScan annotation by default.

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

So the original question turned into how the @ComponentScan annotation works in Spring

2.1 How the @ComponentScan annotation works

In the Spring framework, the processing class corresponding to this annotation is ComponentScanAnnotationParser . The parse method of this class is the main processing logic. The brief processing logic of this method is as follows:

1: Get the basePackage attribute in the @ComponentScan annotation . If not, it defaults to the package path of the class marked by the annotation.

2: Use the scanCandidateComponents method of ClassPathBeanDefinitionScanner to scan all class resource files under classpath:+basePackage+ /*.class**

3: Finally, loop through all the scanned class resource files to determine whether they contain the @Component annotation. If so, register these classes in beandefinitionMap.

Since then, the Java classes written in our code have been scanned by Spring into BeanDefinitions and stored in BeanDefinitionMap. For details of the scanning, you can check out the source code of this class.

3: How Spring creates our Bean instance

After Spring scans the Java class we wrote into a BeanDefinition, it will start to create our Bean instance. Spring will hand over the method of creating the Bean to the _ org.springframework.beans.factory.support.AbstractBeanFactory#getBean _ method. Next Let’s take a look at how the getBean method creates a Bean.

The calling logic of the getBean method is as follows: getBean--> doGetBean --> createBean --> doCreateBean. Finally, Spring will use the org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean method to create the Bean. The main logic of creating a Bean instance is For the four parts: creating a Bean instance, filling in the Bean properties, initializing the Bean, and destroying the Bean , we will explore these four parts separately.

3.1 Create Bean instance

The method entry for creating a Bean instance is as follows:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#__createBeanInstance

if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}

The main logic of this method is: infer the constructor method and parameters that create the Bean, and then use Java reflection to create the Bean instance.

3.2 PopulateBean method populateBean attribute value

In this method, the main purpose is to parse the member attributes that need to be injected into the Bean, and then inject these attributes into the Bean. If the Bean has other dependent Beans, it will first create the dependent Bean, and then return to continue creating the Bean. Note that the circular dependency problem of Bean creation will arise here, which will be explained in detail in Section 6 of this article.

3.4: Initialize Bean (initializeBean method)

Initializing a Bean mainly includes four parts:

3.4.1:invokeAwareMethods

In this method, the methods in the implemented Aware interface are mainly called, including BeanNameAware.setBeanName, BeanClassLoaderAware.setBeanClassLoader, and BeanFactoryAware.setBeanFactory.

Function of the Aware interface: Inject the corresponding Bean in the Spring container into the Bean being created by calling the set method in the Aware interface.

3.4.2: Call the pre-processing method: applyBeanPostProcessorsBeforeInitialization

In this method, the main purpose is to obtain all implementation classes in the Spring container that implement the org.springframework.beans.factory.config.BeanPostProcessor interface, and then call the postProcessBeforeInitialization method in a loop to process the Bean being created.

So in this method we can customize _ BeanPostProcessor _ to extend the functions of Bean and implement our own processing logic

3.4.3: Call Bean-related initialization methods:

3.4.3.1 If it is InitializingBean, call the afterPropertiesSet method

In this process, the Spring framework will determine whether the bean being created implements the InitializingBean interface. If it does, the afterPropertiesSet method will be called to execute the code logic.

3.4.3.2 Call custom initialization method: initMethod

In this process, our custom initialization methods are mainly called, such as the _ init-method and destroy-method configured in the xml file or the @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod") _ method configured using annotations.

3.4.3.3: Call the post-processing method: applyBeanPostProcessorsAfterInitialization

In this method, it is mainly to obtain all the implementation classes in the Spring container that implement the org.springframework.beans.factory.config.BeanPostProcessor interface, and then call postProcessAfterInitialization in a loop to process the Bean being created.

In this method, we can customize _ BeanPostProcessor _ to extend the functions of Bean and implement our own processing logic.

4: Register Bean destruction method

The main thing here is the method that Spring returns when the registered bean is destroyed. For example:

1: The destroy-method method configured in the xml file or the destroyMethod method configured in the @Bean annotation

2: _ destroy _ method in org.springframework.beans.factory.DisposableBean interface

5: Summary

At this point, the creation process from the Java class we wrote to the Bean instance that can be used in the Spring container has been completely sorted out. Understanding the creation process of Bean can make us more familiar with the use of Bean. At the same time, we can also create Bean Add your own processing logic in the process to achieve connecting your own components to the Spring framework

6: Solution to Spring circular dependency

When Spring creates Bean instances, sometimes it is unavoidable that the Java classes we write are interdependent. If Spring does not deal with this interdependence, then an infinite loop problem of creating Bean instances will occur, so Spring is This situation must be handled specially. Let’s explore how Spring skillfully handles the circular dependency problem between beans.

6.1 Expose hook method getEarlyBeanReference

First, for single-instance type beans, when Spring creates the bean, it will expose a hook method in advance to obtain the address reference of the bean being created. The code is as follows:

As shown in the above code, the hook method singletonFactory will be stored in advance in the singletonFactories Map , so that the address reference of this Bean can be exposed in advance. So why does obtaining the address reference need to be packaged into a complex method? It will be explained below

6.2 Other beans obtain the address reference of the bean exposed in advance

When other beans need to depend on the bean being created, the getSingleton method will be called to obtain the address reference of the required bean.

As shown in the appeal code, when obtaining the Bean, it will be obtained from three places.

1: singletonObjects : This is a Map that stores fully created Bean instances.

2: earlySingletonObjects : This is a Map that stores Bean instances created using hook methods exposed in advance.

3: singletonFactories : This is a Map used to store hook methods

When obtaining dependent beans, the hook method getEarlyBeanReference will be called to obtain the reference of the beans exposed in advance. The source code of this method is as follows:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
      }
    return exposedObject;
}



As shown in the source code above, this method mainly requires calling the getEarlyBeanReference method of SmartInstantiationAwareBeanPostProcessor to process the beans that have not yet been created in advance, and the getEarlyBeanReference method has only one logical implementation class **org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator , **This class is the class that creates the Aop proxy. Its code is as follows:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    //提前标记这个bean已经创建过代理对象了
    this.earlyProxyReferences.put(cacheKey, bean);
    //按条件创建代理对象
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}



As shown in the above code, the main goal of this code is to determine whether the bean exposed in advance needs to be a dynamic proxy. If necessary, the dynamic proxy object of the bean exposed in advance will be returned.

So why should we determine whether a dynamic proxy is needed here? Consider the following situation

1: If the dynamic proxy object of this Bean is not returned here, but this Bean will have a dynamic proxy in the subsequent initialization process:

For example: Suppose here that A depends on B, and B depends on A. At this time, B is obtaining the reference exposed by A in advance. If A's own address reference is returned to B at this time, then A's original address reference will be saved in B. When After B is created, when the program returns to create A, A dynamic proxy occurs during the initializing process (initializingBean). At this time, the dynamic proxy object of A is actually used in the Spring container, but B holds the original A reference, then there will be an original reference of A and a reference of A's dynamic proxy in the container, resulting in ambiguity. This is why it is necessary to determine in advance whether a dynamic proxy needs to be created. The problem with this reason is that filling The attribute populateBean process is before the initializing process (initializingBean), and the process of creating a dynamic proxy is during the initializing process.

6.3 Determine whether the Bean’s address has changed

After the Bean is initialized, Spring determines whether the address after the Bean initialization has changed. The code logic is as follows:

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    //判断是否触发了提前创建bean的逻辑(getEarlyBeanReference)
    //如果有其他bean触发了提前创建bean的逻辑,那么这里就不为null
    if (earlySingletonReference != null) {
        //判断引用地址是否发生了变化
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
	}
    }
}



So why does it need to continue to determine whether the Bean's address has changed after initialization?

This is because if there is a circular dependency and additional dynamic proxies occur during the initializing process of the Bean (initializingBean) , for example, in addition to the dynamic proxies that occur in **getEarlyBeanReference, additional dynamic proxies occur. , that is, two dynamic proxies have occurred, then the address of the Bean at this time is different from the address of the Bean generated in the getEarlyBeanReference process. **If this situation is not handled at this time, there will be two simultaneous instances in the Spring container. Different reference objects will cause ambiguity, so Spring needs to avoid this situation.

6.4 If the Bean address changes, determine whether there is a strongly dependent Bean

If Spring encounters the situation described in Section 6.3 during the bean creation process, Spring adopts the following method to handle it:

else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    //获取该Bean依赖的Bean
    String[] dependentBeans = getDependentBeans(beanName);
    Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    //去除因为类型检查而创建的Bean(doGetBean方法typeCheckOnly参数来控制)
    for (String dependentBean : dependentBeans) {
        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
	    actualDependentBeans.add(dependentBean);
        }
    }
    //如果去除因为类型检查而创建的bean之外还存在依赖的bean
    //(这些剩下的bean就是spring实际需要使用的)那么就会抛出异常,阻止问题出现
    if (!actualDependentBeans.isEmpty()) {
	throw new BeanCurrentlyInCreationException(beanName,
            "Bean with name '" + beanName + "' has been injected into other beans [" +
            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
	    "] in its raw version as part of a circular reference, but has eventually been " +
            "wrapped. This means that said other beans do not use the final version of the " +
            "bean. This is often the result of over-eager type matching - consider using " +
            "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
    }
}



The above code is Spring's logic for handling appeals. First of all, it is clear that Spring does not allow appeals to occur . When Spring's reference address of a Bean changes, Spring will first determine whether the dependent Bean has been completely created. If there is a complete The created Bean will throw an exception directly, because the references held in these fully created dependent Beans are already old address references.

The specific processing logic is: first get all the beans that the bean depends on, and then exclude the beans that are created only for type checking from these beans. If after excluding these beans, there are still dependent beans, then these beans may exist For beans that are cyclically dependent and strongly dependent (the reference addresses held in these beans are old addresses, so there will be problems) , Spring will throw exceptions in time to avoid problems.

Author: JD Logistics Zhong Lei

Source: JD Cloud Developer Community Ziyuanqishuo Tech Please indicate the source when reprinting

Lei Jun announced the complete system architecture of Xiaomi's ThePaper OS, saying that the bottom layer has been completely restructured. Yuque announced the cause of the failure and repair process on October 23. Microsoft CEO Nadella: Abandoning Windows Phone and mobile business was a wrong decision. Both Java 11 and Java 17 usage rates exceeded Java 8 Hugging Face was restricted from accessing. The Yuque network outage lasted for about 10 hours and has now returned to normal. Oracle launched Java development extensions for Visual Studio Code . The National Data Administration officially unveiled Musk: Donate 1 billion if Wikipedia is renamed "Weiji Encyclopedia" USDMySQL 8.2.0 GA
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10123189