Moment For Technology

Cyclic dependencies and resolution in Spring

Posted on Dec. 2, 2022, 2:13 p.m. by Ellie Taylor
Category: The back-end Tag: The back-end java spring

What is a cyclic dependency?

Object A depends on object B, and object B depends on object A.

Such as:

Class A{public B B; } class B{public A; }Copy the code

So is circular dependency a problem?

If you leave Spring out of the question, circular dependencies aren't a problem because it's quite normal for objects to depend on each other.

Such as

A a = new A();
B b = new B();

a.b = b;
b.a = a;
Copy the code

So A and B depend on each other.

However, circular dependencies are a problem in Spring. Why?

In Spring, an object is not simply new, but goes through a series of Bean lifecycles, and it is because of the Bean lifecycles that circular dependencies arise. Of course, in Spring, there are many scenarios of cyclic dependency, some of which Spring automatically helps us solve, and some of which need to be solved by programmers, as described in detail below.

To understand cyclic dependencies in Spring, you need to understand the lifecycle of beans in Spring.

The life cycle of the Bean

The life cycle of the Bean is not described in detail here, only in general terms.

The lifecycle of a Bean refers to: How is the Bean generated in Spring?

Objects managed by Spring are called beans. The Bean generation steps are as follows:

1. Spring scans class to get BeanDefinition

2. Generate beans based on the obtained BeanDefinition

3. First, infer the constructor from the class

4. Based on the inferred constructor, reflect, and get an object (temporarily called the original object)

5. Populate the properties in the original object (dependency injection)

6. If a method in the original object is AOP, then a proxy object needs to be generated based on the original object

7. Put the final generated proxy objects into the singleton pool (called singletonObjects in source code), and the next getBean will take directly from the singleton pool

As you can see, there are many steps to the Bean generation process in Spring, and there are many more, such as Aware callback, initialization, and so on, which I won't go into here.

As you can see, in Spring, constructing a Bean includes the new step (step 4, constructor reflection).

After getting a primitive object, Spring needs to inject dependencies into the attributes in the object. What is the injection process?

For example, as mentioned above, class A has A B attribute of class B. Therefore, when class A generates A primitive object, it will assign A value to the B attribute. At this time, it will obtain the singleton bean corresponding to class B from the BeanFactory according to the type and name of the B attribute. If there is a Bean in the BeanFactory that corresponds to B, assign it to B; If no Bean corresponding to B exists in the BeanFactory, we need to generate a Bean corresponding to B and assign it to the B attribute.

In the second case, if class B has not yet generated a Bean in the BeanFactory, it will need to, and it will go through the life cycle of B's Bean.

If A Bean of class B has A property of class A, then A Bean of class A is required during the creation of the Bean of class B. However, the creation of A Bean of class B is triggered by the dependency injection of class A during the creation of the Bean. Therefore, A circular dependency appears here:

ABean creation -- depends on B attribute -- triggers BBean creation --B depends on A attribute -- requires ABean (but ABean is still in the process of creation)

As a result, neither ABean nor BBean can be created.

This is a cyclic dependency scenario, but as mentioned above, there is a mechanism in Spring that helps developers solve part of the cyclic dependency problem, and that mechanism is level-three caching.

Three levels of cache

Level 3 cache is the generic term.

The level 1 cache is singletonObjects

The level 2 cache is earlySingletonObjects

The level 3 cache is singletonFactories

Let's explain what these three caches are for, and look at them in more detail:

Cached in singletonObjects are bean objects that have gone through their full lifecycle.

EarlySingletonObjects is one more early than singletonObjects, indicating that the earlier bean objects are cached. What does early mean? Put the Bean into earlySingletonObjects before the Bean's lifecycle is complete.

SingletonFactories caches the ObjectFactory, which represents the ObjectFactory used to create an object.

Analysis of solving circular dependency

Let's look at why caching can solve circular dependencies.

According to the above analysis, the main reasons for circular dependence are:

When A is created -- requires B----B to create -- requires A, thus creating A loop

So how do you break the loop and add a middleman?

If B's Bean does not exist, then B's Bean does not exist. If B's Bean does not exist, then B's Bean does not exist. Bean, you need to create B and create B process and A Bean, and create A B of the original object first, and then put the original object exposed early B into the cache, then the dependency injection in the original object to B A, this time can get A original object from the cache (although it is A primitive object, is not the final Bean). B's life cycle ends when B's original object dependency injection is complete, and so can A's life cycle.

Since there is only one A primitive object in the whole process, it does not matter to B even if the A primitive object is injected when the attribute is injected, because the A primitive object does not change in the heap in the subsequent life cycle.

You can see from the above analysis that you only need a cache to resolve circular dependencies, so why do you need singletonFactories in Spring?

This is the hard part. Think of a question based on the above scenario: If the original object of A is injected into the properties of B, and the original object of A is AOP to produce A proxy object, then there will be A conflict. In the case of A, its Bean object should actually be A post-AOP proxy object, while B's PROPERTY of A does not correspond to A post-AOP proxy object.

**B depends on A and the final A is not the same object. 六四事件

So how to solve this problem? There is no solution to this problem.

At the end of a Bean's life cycle, Spring provides a BeanPostProcessor to process the Bean, not only modifying the Bean's property values, but also replacing the current Bean.

Here's an example:

@Component public class User { } @Component public class TestBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// Generate a new User object if (beanname.equals (" User ")) {system.out.println (bean); User user = new User(); return user; } return bean; } } public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); User user = context.getBean("user", User.class); System.out.println(user); }}Copy the code

Running the main method produces the following print:

[email protected]
[email protected]
Copy the code

So a beanName corresponding bean object can be completely replaced in BeanPostProcessor.

The execution of BeanPostProcessor is after the property injection in the Bean lifecycle, and the cyclic dependency occurs during the property injection, so it is likely that object A injected into object B and object A after the full life cycle of the Bean are not the same object. That's the problem.

In this case, Spring cannot resolve the circular dependency, because Spring does not know which BeanPostProcessor the object will go through and what will be done to the object when the property is injected.

In which case does Spring address circular dependencies

While this can happen, it certainly happens very rarely, and we don't usually do it during development, it often happens that the final object corresponding to a beanName is not the same object as the original object, and that's AOP.

AOP is done by a BeanPostProcessor, this BeanPostProcessor AnnotationAwareAspectJAutoProxyCreator, Its parent class is AbstractAutoProxyCreator, and AOP in Spring uses either JDK or CGLib dynamic proxies, so if you give an aspect to a method in a class, the class will eventually need to generate a proxy object.

The general process is: Class A - generate A common object - attribute injection - generate A proxy object based on the aspect - put the proxy object into the singletonObjects singleton pool.

AOP can be said to be another major feature of Spring apart from IOC, and circular dependency is in the category of IOC, so Spring needs special treatment if these two features want to coexist.

This is done using singletonFactories, a third level cache.

First, the singletonFactories store an ObjectFactory corresponding to a beanName. In the lifetime of the bean, after generating the original object, you construct an ObjectFactory to store in the singletonFactories. The ObjectFactory is a functional interface, so support Lambda expressions :() - getEarlyBeanReference(beanName, MBD, bean)

The Lambda expression above is an ObjectFactory. Execution of this Lambda expression will result in execution of the getEarlyBeanReference method, which looks like this:

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; }Copy the code

This method can be carried to the getEarlyBeanReference SmartInstantiationAwareBeanPostProcessor method, and the interface implementation class of only two classes implement this approach, One is AbstractAutoProxyCreator, one is InstantiationAwareBeanPostProcessorAdapter, its implementation is as follows:

// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
	return bean;
}

// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	this.earlyProxyReferences.put(cacheKey, bean);
	return wrapIfNecessary(bean, beanName, cacheKey);
}
Copy the code

So obviously, in Spring, the default is that AbstractAutoProxyCreator actually implements the getEarlyBeanReference method, which is used for AOP. The aforementioned AnnotationAwareAspectJAutoProxyCreator is AbstractAutoProxyCreator parent.

So what is the getEarlyBeanReference method doing?

So the first thing you get is a cacheKey, and a cachekey is a beanName.

Then store the beanName and bean (which are the original objects) in earlyProxyReferences

Call wrapIfNecessary for AOP and get a proxy object.

So, when is the getEarlyBeanReference method called? Back to the circular dependency scenario

Text on the left:

The ObjectFactory is the labMDA expression with the getEarlyBeanReference method in the middle. Note that a lambda expression does not execute with a "singletonFactories". That is, the getEarlyBeanReference method is not executed

Text on the right:

Using the ObjectFactory method getEarlyBeanReference, you get an AOP proxy for the original object, using singletonFactories with an ObjectFactory based on A beanName. Then put the proxy object into earlysingtonObjects. Note that you did not put the proxy object into singletonObjects. When did you put the proxy object into singletonObjects?

At this time, we need to understand the role of Earlysingtonobjects. At this time, we only have the proxy object of A primitive object. This object is not complete, because the properties of A primitive object have not been filled, so we cannot directly put the proxy object of A into singletonObjects at this time. So you can only put the proxy object in earlySingletonObjects. Assuming that there are other objects that depend on A, then you can get the proxy object for the original object of A from earlySingletonObjects, and it will be the same proxy object for A.

When B after creating A continuing life cycle, and A complete property after injection, will be conducted in accordance with its own logic to AOP, but now we know that A primary object has experienced the AOP, so for A part, will not go to AOP, so how to judge whether an object through the AOP? Make use of the above-mentioned earlyProxyReferences in AbstractAutoProxyCreator postProcessAfterInitialization method, If beanName is in earlyProxyReferences, it indicates that AOP has been performed in advance, and no need to perform AOP again.

For A, after the AOP judgment and the execution of BeanPostProcessor, it is necessary to put the corresponding object of A into singletonObjects, but we know that the proxy object of A should be put into singletonObjects. So you need to get the proxy object from EarlysingtonObjects and put it into singletonObjects.

The entire cyclic dependency is resolved.

conclusion

To summarize the level 3 cache:

1. SingletonObjects: Cache a beanName corresponding to a bean that has gone through its full lifecycle

2. EarlySingletonObjects: Proxy objects obtained by caching prior to the AOP of the original object. The original object has not been subjected to property injection and subsequent life cycles such as BeanPostProcessor

3. SingletonFactories: The cache is an ObjectFactory, which is used to generate proxy objects after AOP. During the generation of each Bean, a factory is exposed in advance, which may or may not be used. If there are no cyclic dependencies on the Bean, the factory is useless. If there is a cyclic dependency on this bean, then the other bean executes the ObjectFactory to submit an AOP proxy object (if there is AOP, If YOU don't need AOP, you get a raw object).

4. Actually need a cache, is earlyProxyReferences, it is used to record whether a primitive object has been AOP.

Search
About
mo4tech.com (Moment For Technology) is a global community with thousands techies from across the global hang out!Passionate technologists, be it gadget freaks, tech enthusiasts, coders, technopreneurs, or CIOs, you would find them all here.