In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the relevant knowledge of "analyzing the pit of Spring circular dependence". Many people will encounter such a dilemma in the operation of actual cases, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
1. Preface
In the past two days, I have encountered an interesting problem of Spring circular dependency, but this is different from the circular dependency problems I have encountered in the past. It is quite hidden, and few other people have encountered similar problems on the network. Let's call it the atypical Spring circular dependency problem here. But I believe I am definitely not the first to step on this hole, and certainly not the last, probably just because there are few people who have stepped on it and there are few records. So here is the right to record this pit for future generations to view.
As Lu Xun (I) said, "there is no pit in this world, but if more people step on it, it will become a pit."
two。 Typical scene
Often listen to a lot of people have the following comments when Review other people's code: "how can there be circular dependencies between these classes when you design? you will make a mistake!" .
In fact, of course, the first half of this sentence is not wrong, the emergence of circular dependency is indeed a design problem, in theory, circular dependency should be layered, extract the common part, and then rely on the public part by each functional class.
But in the complex code, the manager classes call each other too much, and there is always the problem of circular dependency between some classes. But sometimes we find that when using Spring for dependency injection, although there are circular dependencies between Bean, the code itself is very likely to be normal work, and there doesn't seem to be any bug.
Many sensitive students must have muttered in their hearts, how can circular dependence, which violates the law of causality, happen? Yes, none of this is taken for granted.
3. What is dependence?
In fact, regardless of the scene, it is not accurate or at least meticulous to say that A depends on B in general. We can simply define what dependency is.
The so-called A depends on B, which can be understood to mean that the implementation of some functions in A needs to call other functions in B. It can also be divided into two meanings:
An is strongly dependent on B. Creating an instance of An itself requires B to participate. In real life, it's like your mother gave birth to you.
An is weakly dependent on B. Creating an instance of A does not require B to participate, but A needs to call B's method to implement the function. Contrast in real life is like male farming and female knitting.
So, the so-called circular dependency actually has two meanings:
The circular dependence between strong dependencies.
Circular dependencies between weak dependencies.
Speaking of this level, I think you should know what I want to say.
4. What is relying on mediation?
For strong dependence, An and B cannot be used as prerequisites for each other, otherwise the universe will explode. Therefore, this kind of dependence is currently irreconcilable.
For weak dependence, there is no premise relationship between An and B, An and B just cooperate with each other. Therefore, under normal circumstances, there will be no violation of the law of causality.
So what is the mediation of circular dependence? My understanding is:
The process of changing the weak dependency relationship back to the weak dependency relationship by mistaking it for a strong dependency relationship.
Based on the above analysis, we basically know how Spring mediates circular dependencies (weak dependencies only, strong circular dependencies only God can mediate automatically).
5. Why rely on injection
Most people just implement the IOC container as a "map for storing Bean" and DI as "assign bean to the field in the class through annotations + reflection". In fact, many people ignore the mediation-dependent function of DI. And helping us to rely on mediation itself is an important reason why we use IOC+DI.
In the days when there was no dependency injection, many people would pass dependencies between classes through constructors (which actually constitute strong dependencies). As the project gets larger and larger, it is very easy to develop irreconcilable circular dependencies. At this point, developers are forced to re-abstract, which is very troublesome. In fact, the reason why we turn the original weak dependency into strong dependency is that we couple the three functions of class construction, class configuration and class initialization logic in the constructor.
DI is to help us decouple the function of the constructor.
So how does Spring decouple?
6. Dependency injection model of Spring
There are a lot of related content online in this part, and my understanding is probably the three steps mentioned above:
The construction of the class, calling the constructor, resolving strong dependencies (usually no-parameter construction), and creating class instances.
Class configuration, based on dependency injection related annotations in Field/GetterSetter, parses weak dependencies, and populates all classes that need to be injected.
Class initialization logic that invokes life-cycle initialization methods (such as @ PostConstruct annotations or InitializingBean's afterPropertiesSet methods) to perform the actual initialization business logic.
In this way, the function of the constructor is weakened from the original three to one, only responsible for the construction of the class. The configuration of the class is handed over to DI, and the initialization logic of the class is handed over to the life cycle.
The thought of this layer suddenly solved the problem that I had been stuck in my mind for a long time. When I first started learning Spring, I couldn't figure it out:
Why does Spring have an extra initialization method in the Bean lifecycle in addition to the constructor?
What is the difference between this initialization method and the constructor?
Why does Spring recommend that the initialization logic be written in the initialization method in the life cycle?
Now, if you combine the reliance on mediation, the explanation is very clear:
For dependency mediation, Spring does not inject dependencies when calling the constructor. That is, bean injected through DI cannot be used in the constructor (maybe, but Spring does not guarantee this).
If you don't use the dependency-injected bean in the constructor and just use the parameters in the constructor, there is no problem, but this causes the bean to be strongly dependent on its input parameter bean. Mediation cannot be carried out when subsequent circular dependencies occur.
7. Atypical problem
Conclusion?
Based on the above analysis, we should have reached the following consensus:
Passing dependencies through constructors has the potential to create circular dependencies that cannot be automatically reconciled.
Circular dependencies caused by dependency injection purely through Field/GetterSetter can be automatically mediated.
So I have come to a conclusion that I think is correct. This conclusion worked well until I discovered the scene I encountered this time:
When dependency injection is performed on Bean in Spring, irreconcilable circular dependencies will never be created without constructor injection, when purely circular dependencies are considered.
Of course, I don't mean "constructor injection is not recommended". Instead, I think being able to "use constructor injection gracefully and without introducing circular dependencies" is a more demanding and elegant approach. Implementing this approach requires higher abstraction capabilities and will naturally decouple various functions.
problem
The actual problem encountered is simplified to look like the following (the following classes are in the same package):
@ SpringBootApplication @ Import ({ServiceA.class, ConfigurationA.class, BeanB.class}) public class TestApplication {public static void main (String [] args) {SpringApplication.run (TestApplication.class, args);}} public class ServiceA {@ Autowired private BeanA beanA; @ Autowired private BeanB beanB;} public class ConfigurationA {@ Autowired public BeanB beanB; @ Bean public BeanA beanA () {return new BeanA () }} public class BeanA {} public class BeanB {@ Autowired public BeanA beanA;}
First of all, to be clear, instead of using annotations such as @ Component, @ Configuration, and so on, I manually scan Bean with @ Import to make it easy to specify the initialization order of Bean. Spring loads Bean in the order I @ Import. At the same time, when loading each Bean, if the Bean has a dependency that needs to be injected, it will try to load the Bean on which it depends.
To sort it out, the whole dependency chain looks something like this:
We can find that there is a circular dependency between BeanA,BeanB,ConfigurationA, but don't panic, all dependencies are achieved through non-constructor injection, which in theory seems to be automatically mediated.
But in fact, this code reports the following error:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
This is obviously a circular dependency that Spring cannot mediate.
This is already a little strange. However, if you try to change the location of the BeanA,BeanB declared in the ServiceA class, you will find that the code suddenly works!
Obviously, the order in which the two Bean dependencies are swapped is essentially a change in the order in which the Spring loads the Bean (it is well known that Spring creates the Bean in a single thread).
explain
I believe you have found the problem, yes, the crux of the problem lies in the configuration class ConfigurationA.
A difference between a configuration class and a normal Bean is that in addition to being managed as a Bean, a configuration class can declare other Bean internally.
The problem is that the construction of other Bean declared in the configuration class is actually part of the business logic of the configuration class. In other words, we can create other Bean that he declares only after we have satisfied all the dependencies of the configuration class. Without this restriction, there is a risk of a null pointer if you use your own dependencies when creating other Bean that you declare. )
In this way, BeanA is no longer weakly dependent on ConfigurationA, but really strong (that is, the initialization of ConfigurationA affects not only the dependency filling of BeanA, but also the instance construction of BeanA).
With this understanding, let's analyze the two initialization paths respectively.
Load BeanA first
When Spring tried to load ServiceA, he first constructed ServiceA, and then found that he depended on BeanA, so he tried to load BeanA.
Spring wants to construct BeanA, but finds that BeanA is inside ConfigurationA, so he tries to load ConfigurationA (BeanA is not yet constructed at this time)
Spring constructed an instance of ConfigurationA and found that he depended on BeanB, so he tried to load BeanB.
Spring constructed an instance of BeanB and found that he depended on BeanA, so he tried to load BeanA.
Spring finds that BeanA has not yet been instantiated, and Spring finds itself back to step 2. GG .
Load BeanB first
When Spring tried to load ServiceA, he first constructed ServiceA, and then found that he depended on BeanB, so he tried to load BeanB.
Spring constructed an instance of BeanB and found that he depended on BeanA, so he tried to load BeanA.
Spring finds that BeanA is inside ConfigurationA and attempts to load ConfigurationA (BeanA is not yet constructed at this time)
Spring constructs an instance of ConfigurationA, then finds that he relies on BeanB, and that an instance of BeanB already exists, so populates this dependency into ConfigurationA.
Spring found that ConfigurationA had been constructed and populated with dependencies, so he remembered to construct BeanA.
Spring finds that BeanA already has an instance and gives it to the dependency completion populated by BeanB,BeanB.
Spring goes back to the process of populating dependencies for ServiceA and discovers that it is also dependent on BeanA, so it populates BeanA to ServiceA.
Spring successfully completed the initialization operation.
That's all for analyzing the pit of Spring circular dependency. Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.