[Interview Question] Finally someone can clarify the circular dependency of Spring

table of Contents

What is circular dependency?

Bean life cycle

Level 3 cache

Analysis of the idea of ​​solving circular dependence

What kind of circular dependency does Spring solve?

to sum up


What is circular dependency?

It is very simple, that is, the A object depends on the B object, and the B object depends on the A object.

such as:

// A依赖了B
class A{
    public B b;
}

// B依赖了A
class B{
    public A a;
}

So is circular dependency a problem?

If you don't consider Spring, circular dependencies are not a problem , because it is normal for objects to depend on each other.

such as

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

a.b = b;
b.a = a;

In this way, A and B are dependent on it.

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

Because, in Spring, an object is not simply new, but will go through a series of Bean life cycles. It is because of the Bean life cycle that the circular dependency problem will appear . Of course, in Spring, there are many scenarios where there are circular dependencies. In some scenarios, Spring automatically solves them for us, and some scenarios require programmers to solve them, as described in detail below.

To understand the circular dependency in Spring, you must first understand the life cycle of the Bean in Spring.

 

Bean life cycle

Here will not be a detailed description of the life cycle of the Bean, just describe the general process.

The life cycle of Bean refers to: How is Bean generated in Spring?

The object managed by Spring is called Bean . Bean generation steps are as follows:

class --> BeanDefinition --> new object (original object) --> property filling --> initialization --> bean join singleton pool

  1. Spring scans the class to get BeanDefinition
  2. Generate beans according to the obtained BeanDefinition
  3. First infer the construction method according to the class
  4. According to the inferred construction method, reflection, get an object (temporarily called the original object)
  5. Fill in the properties in the original object (dependency injection)
  6. If a method in the original object is AOP, then you need to generate a proxy object based on the original object
  7. Put the finally generated proxy object into the singleton pool (called singletonObjects in the source code), and get it directly from the singleton pool next time you getBean

As you can see, there are still many steps for the Bean generation process in Spring, and not only the above 7 steps, but also many, such as Aware callback, initialization, etc., which are not discussed in detail here.

It can be found that in Spring, constructing a Bean includes the new step (the fourth step of constructing method reflection).

After getting a primitive object , Spring needs to perform dependency injection on the properties of the object , so what is the injection process?

For example, in class A mentioned above, there is an attribute b of class B in class A. Therefore, when class A generates a primitive object, it will assign a value to the b attribute, which will be based on the type and The attribute name goes to BeanFactory to get the singleton bean corresponding to class B. If there is a Bean corresponding to B in the BeanFactory at this time, it is directly assigned to the b attribute; if there is no Bean corresponding to B in the BeanFactory at this time, a Bean corresponding to B needs to be generated, and then assigned to the b attribute.

The problem arises in the second case. If the corresponding Bean of class B has not been generated in the BeanFactory at this time, then it needs to be generated, and it will go through the life cycle of B's ​​Bean.

Then in the process of creating a Bean of class B, if there is an attribute of class A in class B, then the Bean corresponding to class A is required in the process of creating a Bean of B, but the conditions that trigger the creation of class B Bean It is the dependency injection of Type A Bean during the creation process, so there is a circular dependency here:

ABean creation-->depends on B attribute-->triggers BBean creation--->B depends on A attribute--->ABean is required (but ABean is still in the process of being created)

As a result, ABean cannot be created, and BBean cannot be created either.

This is a cyclic dependency scenario, but as mentioned above, in Spring, some mechanisms help developers solve the problem of partial cyclic dependency. This mechanism is the three-level cache .

 

Level 3 cache

The three-level cache is a general term.

The first level cache is: singletonObjects

The secondary cache is: earlySingletonObjects

The three-level cache is : singletonFactories

Let me briefly explain the role of these three caches, and analyze in detail later:

  • SingletonObjects -->  cached bean objects that have gone through a complete life cycle.
  • earlySingletonObjects -->  one more early than singletonObjects, indicating that the cached bean objects are early. What does early mean? Indicates that the Bean's life cycle has not been completed before the Bean is put into earlySingletonObjects.
  • SingletonFactories -->  cached in ObjectFactory, represents an object factory, used to create an object.

 

Analysis of the idea of ​​solving circular dependence

Let's first analyze why the cache can solve the circular dependency.

According to the above analysis, the main reason for the problem of circular dependence is:

When A is created ---> needs B ---> B to create ---> needs A, thus creating a loop

image.png

So how to break this cycle and add a middleman (cache)

image.png

In the creation process of A's Bean, before dependency injection, the original Bean of A is put into the cache (expose early, as long as it is put in the cache, other Beans can be taken from the cache when needed), and then put into the cache , And then perform dependency injection. At this time, the Bean of A depends on the Bean of B. If the Bean of B does not exist, you need to create the Bean of B. The process of creating the Bean of B is the same as that of A. It also creates a primitive object of B. , And then expose the original object of B into the cache early, and then perform dependency injection A on the original object of B. At this time, the original object of A can be obtained from the cache (although it is the original object of A, it is not the final Bean), after B's original object dependency is injected, the life cycle of B ends, and then the life cycle of A can also end.

Because there is only one original object of A in the whole process, for B, even if the original object of A is injected during property injection, it does not matter, because the original object of A does not occur in the heap in the subsequent life cycle Change ( Java's pass by reference ).

This can be drawn from the above analysis, only one cache can resolve circular dependencies, so why Spring is also required singletonFactories it ?

This is a difficult point. Based on the above scenario, think of a question: if the original object of A is injected into the attribute of B, the original object of A undergoes AOP to generate a proxy object , and then it will appear. For A, its The Bean object should actually be a proxy object after AOP, and the a attribute of B corresponds not to a proxy object after AOP, which creates a conflict.

 

The A that B depends on and the final A are not the same object .

So how to solve this problem? It can be said that there is no solution to this problem.

Because at the end of a Bean's life cycle, Spring provides a  BeanPostProcessor  to process the Bean. This processing can not only modify the attribute value of the Bean, but also replace the current Bean .

for example:

@Component
public class User {
}
@Component
public class LubanBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        // 注意这里,生成了一个新的User对象
        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);

    }
}

Run the main method and get the following print:

com.luban.service.User@5e025e70 
com.luban.service.User@1b0375b3

Therefore, the bean object corresponding to a certain beanName can be completely replaced in the BeanPostProcessor.

BeanPostProcessor performed in Bean's life cycle is in after injecting property of , cyclic dependency occurred in the property injection process , it is likely to cause, inject an object A to object B and after experienced the full life cycle A Object, not an object . This is problematic.

Therefore, the circular dependency in this case cannot be solved by Spring, because during property injection, Spring does not know which BeanPostProcessor the A object will go through and what to do with the A object .

 

What kind of circular dependency does Spring solve?

Although the above situation may happen, it must happen very rarely. We usually do not do this during the development process. However, the final object corresponding to a certain beanName and the original object are not the same but often appear. This is AOP .

AOP is implemented through a BeanPostProcessor , this BeanPostProcessor is AnnotationAwareAspectJAutoProxyCreator , its parent class is AbstractAutoProxyCreator, and in Spring AOP uses either JDK dynamic proxy or CGLib dynamic proxy , so if you give a method in a class Set the aspect, then this class eventually needs to generate a proxy object.

The general process is: Class A --->Generate an ordinary object -->Property injection -->Generate a proxy object based on the aspect -->Put the proxy object into the singletonObjects singleton pool .

And AOP can be said to be another major function of Spring apart from IOC, and circular dependency belongs to the category of IOC, so if the two major functions want to coexist, Spring needs special treatment.

How to deal with it is to use the third-level cache singletonFactories .

First of all, singletonFactories stores the ObjectFactory corresponding to a certain beanName . In the life cycle of the bean, after the original object is generated, an ObjectFactory will be constructed and stored in singletonFactories . This ObjectFactory is a functional interface , so it supports Lambda expressions: () -> getEarlyBeanReference( beanName , mbd , bean )

The above Lambda expression is an ObjectFactory. Executing the Lambda expression will execute the getEarlyBeanReference method, which 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;
}

This method will execute the getEarlyBeanReference method in SmartInstantiationAwareBeanPostProcessor, and only two of the implementation classes under this interface implement this method, one is AbstractAutoProxyCreator and the other 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);
}

So obviously, in the whole Spring, only AbstractAutoProxyCreator implements the getEarlyBeanReference method by default , and this class is used for AOP . The parent class of AnnotationAwareAspectJAutoProxyCreator mentioned above is AbstractAutoProxyCreator.

So what is the getEarlyBeanReference method doing?

First get a cachekey, which is the beanName.

Then store beanName and bean (this is the original object) in earlyProxyReferences

Call wrapIfNecessary for AOP and get a proxy object.

So, when will the getEarlyBeanReference method be called? Back to the cyclic dependency scenario

image.png

Text on the left :

This ObjectFactory is the labmda expression mentioned above. There is a getEarlyBeanReference method in the middle. Note that lambda expressions will not be executed when stored in singletonFactories, that is, the getEarlyBeanReference method will not be executed.

Text on the right :

Obtain an ObjectFactory from singletonFactories according to beanName, and then execute ObjectFactory, that is, execute the getEarlyBeanReference method. At this time, you will get a proxy object of A primitive object after AOP, and then put the proxy object into earlySingletonObjects. Note that there is no proxy at this time Objects are put into singletonObjects, when will they be put into singletonObjects?

At this time, we have to understand the role of earlySingletonObjects. At this time, we only get the proxy object of the original object of A. This object is not complete, because the original object of A has not been filled with attributes, so we cannot directly put the proxy object of A at this time. Put it in singletonObjects, so you can only put the proxy object into earlySingletonObjects. Assuming that other objects depend on A, you can get the proxy object of the original object of A from earlySingletonObjects, and it is the same proxy object of A.

When B is created, A continues to carry out the life cycle, and after A completes the attribute injection, it will perform AOP according to its own logic. At this time, we know that the original object of A has gone through AOP, so for A itself , Will not go to AOP anymore, so how to judge whether an object has experienced AOP? The earlyProxyReferences mentioned above will be used. In the postProcessAfterInitialization method of AbstractAutoProxyCreator, it will determine whether the current beanName is in earlyProxyReferences. If it is, it means that AOP has been performed in advance, and there is no need to perform AOP again.

For A, after the judgment of AOP and the execution of BeanPostProcessor, the object corresponding to A needs to be put into singletonObjects, but we know that A's proxy object should be put into singletonObjects, so at this time Need to get the proxy object from earlySingletonObjects, and then into singletonObjects.

The entire circular dependency is resolved.

 

to sum up

So far, summarize the three-level cache:

  1. singletonObjects : Cache the beans corresponding to a certain beanName that have passed the complete life cycle
  2. earlySingletonObjects : Cache the proxy object obtained after AOP is performed on the original object in advance , the original object has not yet undergone property injection and subsequent life cycles such as BeanPostProcessor
  3. singletonFactories : The cache is an ObjectFactory, which is mainly used to generate the proxy object obtained after the original object is AOP . During the generation of each Bean, a factory is exposed in advance. This factory may or may not be used If there is no circular dependency on the bean, then this factory is useless. The bean is executed according to its own life cycle. After the execution, the bean can be directly put into singletonObjects. If there is a circular dependency that depends on the bean, then That bean executes ObjectFactory submission to get a proxy object after AOP (if there is AOP, if AOP is not needed, get a primitive object directly).
  4. In fact, there is also a cache, earlyProxyReferences , which is used to record whether an original object has undergone AOP.

Original: https://www.yuque.com/renyong-jmovm/kb/dpzl6u

Luban College-Teacher Zhou Yu

●The strongest Tomcat8 performance optimization in history

Why can Alibaba resist 10 billion in 90 seconds? --The evolution of server-side high-concurrency distributed architecture

B2B e-commerce platform--ChinaPay UnionPay electronic payment function

Learn Zookeeper distributed lock, let interviewers look at you with admiration

SpringCloud e-commerce spike microservice-Redisson distributed lock solution

Check out more good articles, enter the official account--please me--excellent in the past

A deep and soulful public account 0.0

Guess you like

Origin blog.csdn.net/a1036645146/article/details/109515701