A deep analysis of Spring circular dependency from the source code level

0 26
Author: Guo YanhongThe following examples are all discussions on singleton modeI...

Author: Guo Yanhong

The following examples are all discussions on singleton mode

A deep analysis of Spring circular dependency from the source code level

Illustration Reference https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce

1. How does Spring create a Bean?

For singleton Beans, there is only one object throughout the entire lifecycle of the Spring container.

Spring uses a three-level cache during the creation of Beans, defined in DefaultSingletonBeanRegistry.java:

/** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
​
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

Taking OneBean under the com.gyh.general package as an example, debug the springboot startup process, and analyze how Spring creates a bean.

Refer to the figure below Spring creates a beanThe process. The most critical steps include:

1.getSingleton(beanName, true)Search the bean object from the first, second, and third-level caches in turn; if the object exists in the cache, return it directly (early);

2.createBeanInstance(beanName, mbd, args)Select a suitable constructor, create an instance object (instance), at this time the properties that depend on the instance are all null, which belongs to a semi-finished product;

3.singletonFactories.put(beanName, oneSingletonFactory)Use the instance from the previous step to build a singletonFactory, and place it in the three-level cache;

4.populateBean(beanName, mbd, instanceWrapper)Populate the bean: create objects or assign values for the properties defined for the bean;

5.initializeBean("one",oneInstance, mbd)Initialize the bean: initialize the bean or perform other processing, such as generating a proxy object (proxy)}

6.getSingleton(beanName, false)Search in the first and second levels of cache sequentially, check if there are any objects generated prematurely due to cyclic dependencies, and whether they are consistent with the initialized objects;

2. How does Spring solve cyclic dependencies?

Taking OneBean and TwoBean under the com.gyh.circular.threeCache package as an example, the two beans are interdependent (i.e., forming a closed loop).

Refer to the figure below Spring solves circular dependenciesFrom the process described above, it can be seen that Spring uses the objectFactory in the three-level cache to generate and return an early object, and expose this early address in advance for other objects to use for dependency injection, thereby solving the problem of cyclic dependency.

3. What cyclic dependencies can Spring not resolve?

3.1 @Async annotation used in a loop

3.1.1 Why does an error occur when @Async is used in a loop?

Taking OneBean and TwoBean under the com.gyh.circular.async package as an example, the two beans are interdependent, and the method in oneBean uses the @Async annotation. In this case, starting Spring fails and the error message is reported as follows:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] 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.

And through debug code, it was found that the error occurs within the AbstractAutowireCapableBeanFactory#doCreateBean method, due to earlySingletonReference != null and exposedObject != bean, resulting in an error.


Combined with the flowchart Spring solves circular dependenciesAs can be seen from the above picture:

1.In line 1, bean is the instance created by createBeanInstance(address1)

2.In line 2, exposedObject is the proxy object generated after initializeBean(address2)

3.In line 3, earlySingletonReference is the object created by getEarlyBeanReference【The address here is the same as bean(address1)】

The deep reason is that TwoBean has already relied on the earlySingletonReference object with the address of address1 during the populateBean process. At this point, after OneBean has been initialized, it returns a new object with the address of address2, causing spring to not know which is the final version of the bean, so an error is reported.

How is earlySingletonReference generated, refer to the getSingleton("one", true) process.

Does using @Async in a loop always result in an error?

Taking OneBean and TwoBean under the com.gyh.circular.async package as an example, the two beans are interdependent, making the methods in TwoBean (non-OneBean) use the @Async annotation. At this point, the spring is started successfully without any errors.

Debug code shows that although TwoBean uses the @Async annotation, its earlySingletonReference is null; therefore, it will not cause any errors.

The underlying reason is that OneBean is created first, followed by TwoBean, and throughout the entire chain, TwoBean's objectFactory has never been searched for in the third-level cache. (OneBean is searched twice during its creation, i.e., one->two->one; TwoBean's creation process only searches it once, i.e., two->one.)

Therefore, the preliminary condition for @Async causing circular dependency errors is:

1.The Bean in circular dependency uses the @Async annotation

2.And this Bean is created before other Beans in the loop.

3.Note: A Bean may exist in multiple loops at the same time; as long as it is the first Bean created in a loop, an error will occur.

3.1.3 Why does using @Transactional in a loop not cause any errors?

It is known that a Bean annotated with @Transactional will also have a proxy generated by Spring, but why does this type of Bean not cause any errors when it is in a loop?

Taking OneBean and TwoBean under the com.gyh.circular.transactional package as an example, the two Beans depend on each other, and the method in OneBean uses the @Transactional annotation. Starting Spring successfully will not cause any errors.

Debug code shows that during the generation of OneBean, although earlySingletonReference is not null, the addresses of exposedObject after initializeBean and the original instance are the same (i.e., no proxy is generated for the instance during the initializeBean step), so no error will occur.

3.1.4 Why does the same proxy generation produce two different phenomena?

The reason why the same proxy generation can produce different phenomena when involved in circular dependencies is that the nodes generating the proxy are different when they are in a circular dependency:

1.@Transactional generates a proxy at getEarlyBeanReference and exposes the address after the proxy (i.e., the final address) early on;

2.@Async generates a proxy at initializeBean, causing the address exposed in advance not to be the final address, resulting in an error.

Why can @Async not generate a proxy at getEarlyBeanReference? By comparing the execution process of the two, it is found that:

Both are wrapping the original instance object in the method AbstractAutoProxyCreator#getEarlyBeanReference, as shown in the figure below

When the Bean using @Transactional is creating a proxy, it gets an advice and then generates the proxy object proxy.

However, when the Bean using @Async is creating a proxy, it does not obtain an advice and cannot be proxied.

3.1.5 Why can @Async not return an advice at getEarlyBeanReference?

Within the method AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean, the main things it does are:

1.Find all Advisors in the current spring container

2.Return all Advisors that match the current bean

The Advisor returned in the first step is BeanFactoryCacheOperationSourceAdvisor and BeanFactoryTransactionAttributeSourceAdvisor, without any Advisor related to Async processing.

Delve deep into the reasons why the first step does not return an Advisor related to Async processing?

It is known that @Async, @Transactional, and @Cacheable need to be enabled in advance, that is, @EnableAsync, @EnableTransactionManagement, and @EnableCaching need to be annotated in advance.

For example, in the annotation definition of @EnableTransactionManagement and @EnableCaching, the Selector class is introduced, which in turn introduces the Configuration class. In the Configuration class, the corresponding Advisor is created and placed in the spring container, so the first step is to get these two Advisors.

The Configuration class introduced in the definition of @EnableAsync creates AsyncAnnotationBeanPostProcessor and not an Advisor, so it will not get it in the first step, so the @Async bean will not be proxied at this step.

3.2 Circular dependency caused by constructor

Taking OneBean and TwoBean under the com.gyh.circular.constructor package as an example, the constructors of the two classes depend on each other. When starting spring, an error is reported:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?

The debug code shows that the two beans are already in a dead loop when they are created according to the constructor, and they cannot expose an available address in advance, so only an error can be reported.

4. How to solve the above circular dependency error?

1.Do not use @Async, and execute the methods that need asynchronous operations in the thread pool. (Recommended)

2.Extract the methods annotated with @Async. (Recommended)

3.Extract the methods using @Async to a separate class, which only does asynchronous processing and does not do other business dependencies, thus avoiding the formation of circular dependencies and solving the error problem. Refer to the com.gyh.circular.async.extract package.

4.Try not to use the constructor to depend on objects. (Recommended)

5.Destroy the loop (not recommended) that does not form a closed loop. Plan the object dependency and method call chain in advance during development to avoid circular dependencies as much as possible. (It is difficult, as the iterative development changes continuously, it is likely to produce loops.)

6.Destroy the creation order (not recommended)

7.Since the class annotated with @Async is created before other classes in the circular dependency, an error will occur. To solve this problem, you can make sure that this class is not created before other classes, such as using @DependsOn and @Lazy.

你可能想看:

5. Collect exercise results The main person in charge reviews the exercise results, sorts out the separated exercise issues, and allows the red and blue sides to improve as soon as possible. The main

In today's rapidly developing digital economy, data has become an important engine driving social progress and enterprise development. From being initially regarded as part of intangible assets to now

Ensure that the ID can be accessed even if it is guessed or cannot be tampered with; the scenario is common in resource convenience and unauthorized vulnerability scenarios. I have found many vulnerab

2021-Digital China Innovation Competition-Huifu Cybersecurity Track-Final-Web-hatenum and source code analysis and payload script analysis

4.5 Main person in charge reviews the simulation results, sorts out the separated simulation issues, and allows the red and blue teams to improve as soon as possible. The main issues are as follows

04/7 The systematic security risks of outsourcing and crowdsourcing are no different from those of formal employees

Data security can be said to be a hot topic in recent years, especially with the rapid development of information security technologies such as big data and artificial intelligence, the situation of d

In-depth Analysis: Mining Trojan Analysis and Emergency Response Disposal Under a Complete Attack Chain

Analysis of a Separated Storage and Computing Lakehouse Architecture Supporting Multi-Model Data Analysis Exploration (Part 1)

Bubba AI launches open-source compliance platform Comp AI, helping 100,000 startups achieve security compliance

最后修改时间:
admin
上一篇 2025年03月26日 13:56
下一篇 2025年03月26日 14:19

评论已关闭