In-depth analysis of Spring circular dependencies from the source code level

以下举例皆针对单例模式讨论

Diagram reference spring | ProcessOn free online drawing, online flow chart, online mind map|

1. How does Spring create beans?

For a singleton bean, there is one and only one object in the entire life cycle of the Spring container.

In the process of creating beans, Spring uses the three-level cache, which is 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);

Take OneBean under the com.gyh.general package as an example, debug the springboot startup process, and analyze how spring creates beans.

Refer to the process of creating beans in spring in the figure . The most critical steps are:

  1. getSingleton(beanName, true)Search for bean objects from the first, second and third-level caches in turn, and return directly (early) if there is an object in the cache;
  1. createBeanInstance(beanName, mbd, args)Choose a suitable constructor, new instance object (instance), at this time, the dependent attributes in the instance are still null, which is a semi-finished product;
  1. singletonFactories.put(beanName, oneSingletonFactory)Use the instance in the previous step to build a singletonFactory and put it in the third-level cache;
  1. populateBean(beanName, mbd, instanceWrapper)Populate the bean: create an object or assign a value to the property defined by the bean;
  1. initializeBean("one",oneInstance, mbd)Initialize beans: initialize or otherwise process beans, such as generating proxy objects (proxy);
  1. getSingleton(beanName, false)Search in the primary and secondary caches in turn to check whether there are objects generated in advance due to circular dependencies, and if so, whether they are consistent with the initialized objects;

2. How does Spring solve circular dependencies?

Take OneBean and TwoBean under the com.gyh.circular.threeCache package as an example, the two Beans depend on each other (that is, form a closed loop).

Referring to the process of spring solving circular dependency in the figure , we can see that spring uses the objectFactory in the three-level cache to generate and return an early object, and expose the early address in advance for other object dependency injection to solve the circular dependency problem.

3. What circular dependencies can Spring not resolve?

3.1 The @Async annotation is used in the loop

3.1.1 Why is there an error when @Async is used in the loop?

Take OneBean and TwoBean under the com.gyh.circular.async package as an example. The two beans depend on each other, and the method in oneBean uses the @Async annotation. At this time, the startup of spring fails, and the error message is: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 the debug code, it is found that the error reporting location is in the AbstractAutowireCapableBeanFactory#doCreateBean method, because earlySingletonReference != null and exposedObject != bean, resulting in an error.





Combined with spring in the flow chart to solve circular dependencies and the above picture, we can see:

  1. The bean in line 1 is the instance created by createBeanInstance (address1)
  1. The exposedObject in line 2 is the proxy object (address2) generated after initializeBean
  1. The object created when earlySingletonReference in line 3 is getEarlyBeanReference [the address here is the same as bean (address1)]

The underlying reason is: Previously, TwoBean had already relied on the earlySingletonReference object whose address was address1 when populateBean, but at this time, OneBean returned a new object whose address was address2 after initializeBean, causing spring to not know which is the final version of the bean, so it reported an error .

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

3.1.2 Will there be an error if @Async is used in the loop?

Still take OneBean and TwoBean under the com.gyh.circular.async package as an example. The two beans depend on each other, so that the method in TwoBean (not OneBean) uses the @Async annotation. At this time, the spring is started successfully and no error is reported.

The debug code shows that although TwoBean uses the @Async annotation, its earlySingletonReference = null; so it will not cause an error.





The underlying reason is: OneBean is created first, and TwoBean is created later. In the whole link, the objectFactory of TwoBean has not been searched in the third-level cache. (OneBean was found twice during the creation process, that is, one-> two -> one; during the creation of TwoBean, it was only found once, that is, two -> one.)

It can be obtained from this: @Async causes circular dependency error reporting conditions:

  1. The beans in the circular dependency use the @Async annotation
  1. And this bean is created before other beans in the loop.
  1. Note: A bean may exist in multiple cycles at the same time; as long as it is the first bean created in a cycle, an error will be reported.

3.1.3 Why is @Transactional used in the loop and no error is reported?

It is known that Spring will also generate a proxy object for a bean annotated with @Transactional, but why does this kind of bean not generate an error when it is in a loop?

Take 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. Spring starts successfully and no error is reported.

The debug code shows that in the process of generating OneBean, although earlySingletonReference != null, the exposedObject after initializeBean has the same address as the original instance (that is, no proxy is generated for the instance in the initializeBean step), so no error will be reported.







3.1.4 Why are the same agents producing two different phenomena?

Proxy objects are also generated, and they also participate in circular dependencies. The reason for different phenomena is that when they are in circular dependencies, the nodes that generate proxies are different:

  1. @Transactional generates a proxy when getEarlyBeanReference, and exposes the address after the proxy (that is, the final address) in advance;
  1. @Async generates a proxy when initializeBean, resulting in the address exposed in advance is not the final address, resulting in an error.

Why can't @Async generate a proxy when getEarlyBeanReference? Comparing the code process executed by the two, it is found that:

Both of them wrap the original instance object in the method of AbstractAutoProxyCreator#getEarlyBeanReference, as shown in the figure below





When the Bean using @Transactional gets an advice when creating proxy, the proxy object proxy is generated immediately.





However, the Bean using @Async does not get advice when creating proxy and cannot be proxied.





3.1.5 Why @Async cannot return an advice when getEarlyBeanReference?

In the AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean method, the main things to do are:

  1. Find all Advisors in the current spring container
  1. Returns all Advisors that match the current bean

The Advisors returned in the first step include BeanFactoryCacheOperationSourceAdvisor and BeanFactoryTransactionAttributeSourceAdvisor, and there is no Advisor dealing with Async.

Get to the bottom of it and find out why the first step doesn't return to deal with Async-related Advisor?

It is known that the use of @Async @Transactional @Cacheable needs to be enabled in advance, that is, @EnableAsync, @EnableTransactionManagement, @EnableCaching should be marked in advance.

Taking @EnableTransactionManagement and @EnableCaching as examples, the Selector class is introduced in its annotation definition, and the Configuration class is introduced in the Selector. In the Configuration class, the corresponding Advisor is created and placed in the spring container, so the first step can be obtained These two Advisors.

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

3.2 Circular dependencies caused by constructors

Take OneBean and TwoBean under the com.gyh.circular.constructor package as an example. The constructors of the two classes depend on each other. When spring is started, 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 have fallen into an infinite loop when they are based on the constructor new instance, and cannot expose the available addresses in advance, so they can only report an error.



4. How to solve the above circular dependency error?

  1. Instead of @Async, put methods that require asynchronous operations in the thread pool for execution. (recommend)
  2. Propose methods annotated with @Async. (recommend)
  3. Propose the method using @Async into a separate class, which only does asynchronous processing and does not make other business dependencies, that is, avoids the formation of circular dependencies, thereby solving the problem of error reporting. See com.gyh.circular.async.extract package.
  4. Try not to use constructor dependent objects. (recommend)
  5. Breaking the cycle (not recommended) does not form a closed loop. Before development, plan object dependencies and method call chains, and try not to use circular dependencies. (difficult, as iterative development continues to change, it is likely to generate cycles)
  6. Break creation order (not recommended)
  7. Because the class annotated with @Async will report an error when it is created before other classes in the circular dependency, then find a way to make this class not be created before other classes, and this problem can also be solved, such as: @DependsOn, @Lazy

Guess you like

Origin blog.csdn.net/changlina_1989/article/details/125918409