Circular dependencies in Spring will be interviewed by Ali, Tencent, and 90% of ByteDance asked

Preface

Circular dependency in Spring has always been a very important topic in Spring. On the one hand, because the source code has done a lot of processing to solve the circular dependency, on the other hand, it is because during the interview, if you ask higher-level questions in Spring, Then the circular dependency must not escape. If you answer well, then this is your nirvana. Anyway, it is the interviewer’s nirvana. This is also the reason for this title. Of course, the purpose of this article is to allow you to be able to perform in all subsequent interviews. One more nirvana, specially used to kill the interviewer!

The core idea of ​​this article is,

When the interviewer asks:

"Please talk about the circular dependency in Spring."

How should we answer?

Mainly divided into the following points

What is circular dependency?

Under what circumstances can circular dependencies be handled? How does Spring resolve circular dependencies? At the same time, this article hopes to correct several wrong statements about circular dependencies that often appear in the industry.

Only in the case of setter injection, the circular dependency can be solved (wrong) The purpose of the third-level cache is to improve efficiency (wrong) OK, the paving has been done, then we start the text

What is circular dependency? Literally understood that while A depends on B, B also depends on A, as shown below

 

 

 

Reflected in the code level is like this

 
  1. @Component

  2. public class A {

  3. // A中注入了B

  4. @Autowired

  5. private B b;

  6. }

  7.  
  8. @Component

  9. public class B {

  10. // B中也注入了A

  11. @Autowired

  12. private A a;

  13. }

Of course, this is the most common kind of circular dependency, and there are more special ones

 
  1. // 自己依赖自己

  2. @Component

  3. public class A {

  4. // A中注入了A

  5. @Autowired

  6. private A a;

  7. }

Although the manifestations are different, they are actually the same problem----->cyclic dependency

Under what circumstances can circular dependencies be handled?

Before answering this question, we must first make it clear that Spring has preconditions for solving circular dependencies.

Beans with circular dependencies must be singleton dependency injection, not all constructor injection (many blogs say that it can only solve the circular dependency of the setter method, which is wrong) The first point should be well understood. The second point: What does it mean to not be all constructor injection? We still use code to speak

 
  1. @Component

  2. public class A {

  3. // @Autowired

  4. // private B b;

  5. public A(B b) {

  6.  
  7. }

  8. }

  9.  
  10.  
  11. @Component

  12. public class B {

  13.  
  14. // @Autowired

  15. // private A a;

  16.  
  17. public B(A a){

  18.  
  19. }

  20. }

In the above example, the way to inject B into A is through the constructor, and the way to inject A into B is also through the constructor. At this time, the circular dependency cannot be resolved. If you have two such interdependent beans in your project , The following error will be reported at startup:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

In order to test the relationship between the solution of the circular dependency and the injection method, we do the following four tests

 

 

 

The specific test code is simple, so I won't let it go. From the above test results, we can see that the circular dependency is not only resolved in the case of setter method injection, even in the case of constructor injection, the circular dependency can still be processed normally.

So why exactly? How does Spring deal with circular dependencies? Don't worry, let's look down

How does Spring resolve circular dependencies?

The solution to circular dependencies should be discussed in two cases

Simple circular dependency (no AOP)

Combining the circular dependency of AOP Simple circular dependency (without AOP) Let’s analyze one of the simplest examples first, which is the demo mentioned above

 
  1. @Component

  2. public class A {

  3. // A中注入了B

  4. @Autowired

  5. private B b;

  6. }

  7.  
  8. @Component

  9. public class B {

  10. // B中也注入了A

  11. @Autowired

  12. private A a;

  13. }

Through the above, we have already known that the circular dependency in this case can be solved, so what is the specific process? We analyze step by step

First of all, we need to know that when Spring creates Beans by default, they are created according to the natural ordering, so the first step Spring will create A.

At the same time, we should know that Spring is divided into three steps in the process of creating Bean

Instantiation, corresponding method: createBeanInstance method in AbstractAutowireCapableBeanFactory

Property injection, corresponding method: PopulateBean method of AbstractAutowireCapableBeanFactory

Initialization, corresponding method: initializeBean of AbstractAutowireCapableBeanFactory

These methods have been explained in detail in the previous source code analysis articles. If you have not read my article before, then you only need to know

Instantiation, a simple understanding is that new has an object attribute injection, fills the attribute initialization for the new object in the instantiation, executes the methods in the aware interface, the initialization method, and completes the AOP proxy. Based on the above knowledge, we begin to interpret the entire circular dependency processing The whole process should start with the creation of A. As mentioned above, the first step is to create A!

 

 

The process of creating A is actually calling the getBean method, which has two meanings

Create a new Bean. Obtain the created object from the cache. What we are analyzing now is the first meaning, because there is no A in the cache at this time!

GetSingleton (beanName)

First call getSingleton(a) method, this method will call getSingleton(beanName, true), in the above figure I omitted this step

 
  1. public Object getSingleton(String beanName) {

  2. return getSingleton(beanName, true);

  3. }

The getSingleton(beanName, true) method is actually to try to get the Bean from the cache. The entire cache is divided into three levels

SingletonObjects, the first-level cache, stores all the created singleton Bean earlySingletonObjects, which are instantiated, but the singletonFactories that have not yet been property injected and initialized, are a singleton factory exposed in advance. The second-level cache is stored The object obtained from this factory is created for the first time because A is created for the first time, so no matter which cache is there, it will enter another overloaded method getSingleton(beanName, singletonFactory) of getSingleton.

Get Singleton (beanName, singletonFactory)

This method is used to create Bean, and its source code is as follows:

 
  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {

  2. Assert.notNull(beanName, "Bean name must not be null");

  3. synchronized (this.singletonObjects) {

  4. Object singletonObject = this.singletonObjects.get(beanName);

  5. if (singletonObject == null) {

  6.  
  7. // ....

  8. // 省略异常处理及日志

  9. // ....

  10.  
  11. // 在单例对象创建前先做一个标记

  12. // 将beanName放入到singletonsCurrentlyInCreation这个集合中

  13. // 标志着这个单例Bean正在创建

  14. // 如果同一个单例Bean多次被创建,这里会抛出异常

  15. beforeSingletonCreation(beanName);

  16. boolean newSingleton = false;

  17. boolean recordSuppressedExceptions = (this.suppressedExceptions == null);

  18. if (recordSuppressedExceptions) {

  19. this.suppressedExceptions = new LinkedHashSet<>();

  20. }

  21. try {

  22. // 上游传入的lambda在这里会被执行,调用createBean方法创建一个Bean后返回

  23. singletonObject = singletonFactory.getObject();

  24. newSingleton = true;

  25. }

  26. // ...

  27. // 省略catch异常处理

  28. // ...

  29. finally {

  30. if (recordSuppressedExceptions) {

  31. this.suppressedExceptions = null;

  32. }

  33. // 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除

  34. afterSingletonCreation(beanName);

  35. }

  36. if (newSingleton) {

  37. // 添加到一级缓存singletonObjects中

  38. addSingleton(beanName, singletonObject);

  39. }

  40. }

  41. return singletonObject;

  42. }

  43. }

In the above code, we mainly grasp one point. The Bean returned by the createBean method is finally placed in the first-level cache, which is the singleton pool.

So here we can draw a conclusion: what is stored in the first-level cache is a singleton bean that has been completely created

Call addSingletonFactory method

As shown below:

 

 

After instantiating the Bean, Spring wraps the Bean into a factory and adds it to the three-level cache before attribute injection. The corresponding source code is as follows:

 
  1. // 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)

  2. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {

  3. Assert.notNull(singletonFactory, "Singleton factory must not be null");

  4. synchronized (this.singletonObjects) {

  5. if (!this.singletonObjects.containsKey(beanName)) {

  6. // 添加到三级缓存中

  7. this.singletonFactories.put(beanName, singletonFactory);

  8. this.earlySingletonObjects.remove(beanName);

  9. this.registeredSingletons.add(beanName);

  10. }

  11. }

  12. }

Here is just adding a factory, an object can be obtained through the getObject method of this factory (ObjectFactory), and this object is actually created through the getEarlyBeanReference method. So, when will the getObject method of this factory be called? This time is about to create the process of B.

When A is instantiated and added to the three-level cache, it is necessary to start property injection for A. When it is injected, it is found that A depends on B. Then Spring will go to getBean(b) again at this time, and then reflection call setter The method completes the attribute injection.

 

 

Because B needs to be injected into A, when B is created, getBean(a) will be called again. At this time, it will return to the previous process, but the difference is that the previous getBean is to create the Bean. Calling getBean is not to create it, but to get it from the cache, because A has put it in the third-level cache singletonFactories after instantiation, so the process of getBean(a) is like this at this time

 

 

From here we can see that A injected into B is an object exposed in advance through the getEarlyBeanReference method, not a complete Bean, so what does getEarlyBeanReference do? Let’s look at its source code

 
  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

  2. Object exposedObject = bean;

  3. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {

  4. for (BeanPostProcessor bp : getBeanPostProcessors()) {

  5. if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {

  6. SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;

  7. exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

  8. }

  9. }

  10. }

  11. return exposedObject;

  12. }

It actually calls getEarlyBeanReference of the post processor, and there is only one post processor that actually implements this method, which is the AnnotationAwareAspectJAutoProxyCreator imported through the @EnableAspectJAutoProxy annotation. In other words, if you do not consider AOP, the above code is equivalent to:

 
  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

  2. Object exposedObject = bean;

  3. return exposedObject;

  4. }

That is to say, the factory did nothing but returned the objects created in the instantiation stage directly! So is the three-level cache useful without considering AOP? To be reasonable, it's really useless. Isn't it all right for me to put this object in the second-level cache? If you say it improves efficiency, then tell me where is the improved efficiency?

So what exactly does the third-level cache do? Don't worry, let's finish the whole process first, and you can realize the role of the third-level cache when you analyze the circular dependency with AOP below!

At this point, I don’t know if my friends will have any questions. Will it be a problem to inject an uninitialized type A object into B in advance?

Answer: No

At this time, we need to complete the process of creating the Bean A, as shown in the following figure:

 

 

 

From the above figure, we can see that although B is injected with an uninitialized A object in advance when B is created, the reference to the A object injected into B is always used in the process of creating A, and then A will be initialized based on this reference, so there is no problem.

Combined with the circular dependency of AOP

As we have said before, in the case of ordinary circular dependencies, the three-level cache has no effect. The three-level cache is actually related to AOP in Spring. Let's take a look at the code of getEarlyBeanReference:

 
  1. protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {

  2. Object exposedObject = bean;

  3. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {

  4. for (BeanPostProcessor bp : getBeanPostProcessors()) {

  5. if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {

  6. SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;

  7. exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);

  8. }

  9. }

  10. }

  11. return exposedObject;

  12. }

If AOP is turned on, then the getEarlyBeanReference method of AnnotationAwareAspectJAutoProxyCreator is called. The corresponding source code is as follows:

 
  1. public Object getEarlyBeanReference(Object bean, String beanName) {

  2. Object cacheKey = getCacheKey(bean.getClass(), beanName);

  3. this.earlyProxyReferences.put(cacheKey, bean);

  4. // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象

  5. return wrapIfNecessary(bean, beanName, cacheKey);

  6. }

Going back to the above example, if we perform AOP proxying on A, then getEarlyBeanReference will return a proxy object instead of the object created in the instantiation phase, which means that the injected A in B will be a proxy object Instead of the object created in the instantiation phase of A.

 

 

Seeing this picture, you may have the following questions

Why inject a proxy object when injecting B? Answer: When we perform AOP proxy for A, it means that we want to get the proxy object of A from the container instead of A itself, so when we inject A as a dependency, we must also inject its proxy object.

Obviously it is the A object when it is initialized, so where does Spring put the proxy object into the container?

 

 

After the initialization is completed, Spring calls the getSingleton method again. The parameters passed in this time are different. False can be understood as disabling the three-level cache. As mentioned in the previous figure, it has already been injected into B. The factory in the third-level cache is taken out, and an object obtained from the factory is put into the second-level cache, so the time for the getSingleton method here is to get the proxy A object from the second-level cache. exposedObject == bean can be considered to be true, unless you have to replace the Bean in the normal process in the post processor of the initialization phase, for example, add a post processor:

 
  1. @Component

  2. public class MyPostProcessor implements BeanPostProcessor {

  3. @Override

  4. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

  5. if (beanName.equals("a")) {

  6. return new A();

  7. }

  8. return bean;

  9. }

  10. }

However, please don't do this kind of show operation, which will increase your troubles!

When initializing, the A object itself is initialized, and the container and injected into B are all proxy objects. Will this be a problem? Answer: No, this is because whether it is a proxy class generated by a cglib proxy or a jdk dynamic proxy, there is a reference to the target class inside. When the method of the proxy object is called, the method of the target object is actually called, and A is complete Initialization is equivalent to the initialization of the proxy object itself

Why does the third-level cache use factories instead of direct references? In other words, why do you need this third-level cache, can't you directly expose a reference through the second-level cache? Answer: The purpose of this factory is to delay the proxy of the objects generated in the instantiation stage. Only when the circular dependency occurs, will the proxy objects be generated in advance, otherwise only a factory will be created and placed in the third-level cache. But will not actually create objects through this factory

Let's consider a simple situation. Take the creation of A separately as an example. Assuming that there is no dependency between AB, but A is proxied, at this time, when A is instantiated, it will enter the following code:

 
  1. // A是单例的,mbd.isSingleton()条件满足

  2. // allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足

  3. // isSingletonCurrentlyInCreation:正在在创建A,也满足

  4. // 所以earlySingletonExposure=true

  5. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&

  6. isSingletonCurrentlyInCreation(beanName));

  7. // 还是会进入到这段代码中

  8. if (earlySingletonExposure) {

  9. // 还是会通过三级缓存提前暴露一个工厂对象

  10. addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

  11. }

As you can see, even if there is no circular dependency, it will be added to the third-level cache, and it has to be added to the third-level cache, because so far Spring cannot determine whether this bean has a circular dependency with other beans. .

Assuming that we directly use the second-level cache here, it means that all Beans must complete the AOP proxy in this step. Is this necessary?

Not only is it unnecessary, but it also violates Spring's design of combining AOP and Bean's life cycle! The life cycle of Spring's combination of AOP and Bean itself is completed by the post processor of AnnotationAwareAspectJAutoProxyCreator, and the AOP proxy is completed for the initialized Bean in the postProcessAfterInitialization method of this postprocessing. If there is a circular dependency, there is no way, but to create a proxy for the Bean first, but when there is no circular dependency, the design is to let the Bean complete the proxy at the last step of the life cycle instead of completing the proxy immediately after instantiation .

Does the three-level cache really improve efficiency?

Now we already know the real role of the third-level cache, but this answer may not convince you, so we will summarize and analyze the last wave, does the third-level cache really improve the efficiency? The discussion is divided into two points:

The circular dependency between beans without AOP can be seen from the above analysis, in this case the three-level cache is useless at all! So there won’t be any claims to improve efficiency

The circular dependency between AOP beans takes our A and B as an example, where A is proxied by AOP, we first analyze the creation process of A and B when the three-level cache is used

 

 

 

Assuming that the third-level cache is not used, directly in the second-level cache

 

 

 

The only difference between the above two processes is that the time to create a proxy for the A object is different. When the third-level cache is used, the time to create the proxy for A is when A needs to be injected into B. If the third-level cache is not used, then After A is instantiated, it needs to create an agent for A immediately and put it in the second-level cache. For the entire creation process of A and B, the time consumed is the same

In summary, no matter what the situation is, the statement that the three-level cache improves efficiency is wrong!

to sum up

Interviewer: "How does Spring solve circular dependencies?"

Answer: Spring solves the circular dependency through the three-level cache. The first-level cache is the singleton object pool (singletonObjects), the second-level cache is the early SingletonObjects, and the third-level cache is the early exposure object factory (singletonFactories). When a circular reference occurs between A and B, after A is instantiated, the instantiated object is used to create an object factory and added to the three-level cache. If A is proxied by AOP, then through this factory What is obtained is the object after A is proxied. If A is not proxied by AOP, then what the factory obtains is the object instantiated by A. When A performs attribute injection, B will be created, and B depends on A, so when B is created, getBean(a) will be called to obtain the required dependencies. At this time, getBean(a) will be obtained from the cache. , The first step is to get the factory in the third-level cache; the second step is to call the getObject method of the object factory to get the corresponding object, and then inject it into B after getting this object. Then B will complete its life cycle process, including initialization, post processor, etc. When B is created, B will be injected into A, and A will complete its entire life cycle. At this point, the circular dependency is over!

Interviewer: "Why use a third-level cache? Can the second-level cache solve circular dependencies?"

Answer: If you want to use the second-level cache to solve the circular dependency, it means that all Beans must complete the AOP proxy after instantiation, which violates the principle of Spring design. At the beginning of the design, Spring used the AnnotationAwareAspectJAutoProxyCreator to perform the Bean The last step of the life cycle is to complete the AOP proxy, rather than immediately perform the AOP proxy after instantiation.

A question

Why can the circular dependency of the third case in the following table be resolved, but the fourth case cannot be resolved?

Tip: When Spring creates Beans by default, it will be created according to the natural ordering, so A will be created before B

 

Guess you like

Origin blog.csdn.net/yuandengta/article/details/109258929