Happy Moment
A girl added me the other day. I agreed.
On the first day, she and I talked about literature, ideals, basketball, kittens and puppies
The next day, she told me she wanted to see my abs.
Scared me, I backhanded it and deleted and pulled the plug, I'm so meow a belly full of fat, where are my abs!
circular dependency
I've written four articles on Spring's circular dependencies
Spring's circular dependencies, a detailed analysis of the source code → Do you really have to have a three-level cache?
Revisiting Circular Dependencies → How Spring Determines Prototype Circular Dependencies and Constructor Circular Dependencies
Triple checking circular dependencies → Remembering an occasional circular dependency problem on the line
Four Explorations of Circular Dependencies → When circular dependencies meet BeanPostProcessor, love may be in the air!
Are you guys panicking a little bit at this point, or are you going to come to the end of the five probes? Let me give you a shot in the arm. Instead of talking about cyclic dependencies, let's look at a tidbit of information we encountered during the debugging of a cyclic dependency.
First of all, this is the material from a fellow gardener (@FlyingSlowBullfrog), and has been approved by him.
The circular dependency case is simple
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="/POM/4.0.0"
xmlns:xsi="http:///2001/XMLSchema-instance"
xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId></groupId>
<artifactId>spring-circle</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-circle-async</artifactId>
<properties>
<>8</>
<>8</>
<>UTF-8</>
</properties>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>
Spring's version uses:5.2.
/**
* @author: greenstone road
*/
@Component
public class Circle {
@Autowired
private Loop loop;
public Loop getLoop() {
return loop;
}
public void sayHello(String name) {
("circle sayHello, " + name);
}
}
/**
* @author: greenstone road
*/
@Component
public class Loop {
@Autowired
@Lazy
private Circle circle;
public Circle getCircle() {
return circle;
}
public void sayHello(String name) {
("loop sayHello, " + name);
}
}
To make it compatible with various versions of Spring, the@Lazy
/**
* @author: greenstone road
*/
@ComponentScan(basePackages = "")
public class CircleTest {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext();
Circle circle = ();
Loop loop = ();
(());
(loop);
}
}
main
It's okay to run.
Full Code:spring-circle-async
Debugging Episode
I want to see how Spring handles cyclic dependencies, and I want to see how Spring handles cyclic dependencies.AbstractAutowireCapableBeanFactory#doCreateBean
line 606, and also add aCondition
To start debugging, to make it easier to see what's in the three-level cache, we add threewatch
Add all three levels of cache
At this point, let's look at the second level of the cacheearlySingletonObjects
is devoid of content, let's look at the third level of caching
How circle got to the third level of the cache has to do with circular dependencies; next go to the first level of the cache and find theloop
touch brieflycircle
(used form a nominal expression)toStrng()
And then weF8
One more time (line 606 of the code executes, comes to line 607, and line 607 does not execute), and then go to the second level of the cache
It's amazing that the second level cache has elements, so the third level cache of thecircle
Does it still exist?
Obviously, there is some operation that puts the third level of the cache in thecircle
It's early exposure to the second level of caching, review what operations we did in the meantime?
- Tapped circle's toString()
- F8, executed code line 606: if (earlySingletonExposure)
This is obvious, must be the circle of the point toString() lead to, how to verify? In fact, it is very simple, start debugging again, come to AbstractAutowireCapableBeanFactory 606 lines, nothing to move, directly in theDefaultSingletonBeanRegistry#getSingleton
182 Line breaks.
Then go back to AbstractAutowireCapableBeanFactory 606 and look for the loop in the first level of cache, then click on the toString of its circle, IDEA will prompt the following message
Skipped breakpoint at :182 because it happened inside debugger evaluation Troubleshooting guide
Translated.
The :182 breakpoint is ignored because it occurs inside the debugger, for details seeTroubleshooting guide
What's the point of exposing the program early, letting go of the breakpoints, and the program executing normally? So I'm going to give you a little more information.@EnableAsync
/**
* @author: greenstone road
*/
@ComponentScan(basePackages = "")
@EnableAsync
public class CircleTest {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext();
Circle circle = ();
Loop loop = ();
(());
(loop);
}
}
to the sayHello method of@Async
/**
* @author: greenstone road
*/
@Component
public class Circle {
@Autowired
private Loop loop;
public Loop getLoop() {
return loop;
}
@Async
public void sayHello(String name) {
("circle sayHello, " + name);
}
}
Repeat the previous debugging process (remember to go to the first level of cache in theloop
(used form a nominal expression)circle
and then click on itstoString()
), after canceling all breakpointsF9
,BeanCurrentlyInCreationException
It's coming.
Exception in thread "main" : Error creating bean with name 'circle': Bean with name 'circle' has been injected into other beans [loop] 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.
at (:623)
at (:516)
at $doGetBean$0(:324)
at (:234)
at (:322)
at (:202)
at (:897)
at (:879)
at (:551)
at .<init>(:89)
at (:16)
The anomaly message says it all.
Error creating bean named circle: the loop bean is injected with a proxy instance of circle, not the circle bean that ends up in the first level of the cache.
This is equivalent to injecting a proxy instance of circle into the loop bean, while exposing a semi-finished object of circle in advance, which is inconsistent; the reason for this is that we manipulated circle's toString, which caused the semi-finished object to be exposed in advance.
Let's sort it out.Circle
cap (a poem)Loop
The instance creation process of the According to theSpring
The scanning rule is that Circle is scanned first.
Triple checking circular dependencies → Remembering an occasional circular dependency problem on the line There is an introduction to scanning rules
the reason whyCircle
instance will be created first because the@Async
(underlying implementation: proxy), the third level of caching creates the Circle proxy object in advance
Then fill in the properties of the Circle semi-finished objectLoop loop
The third level of caching creates the Loop proxy in advance (it's not used, so you can just remove it later).
At this point we look at the stack frame of the current thread
Then fill in the properties of the Loop semi-finished objectCircle circle
At this point, the circle hasn't been created yet, so the circle that fills the loop must be a proxy for the circle in the third level of the cache.
Once populated, the loop instance is created, added to the first level of cache, and the loops in the third level of cache are removed (echoing what was said earlier: they are not used, and are subsequently removed) and the loops in the second level of cache are removed (they are not)
At this point, the loop comes to the first level of the cache and becomes thea finished product
instance, while the circle is still in the third-level cache and the second-level cache is still empty; after the loop instance is created, go back to filling the circle's attributes, and fill the loop product with the semi-finished circle
When the initialization of circle is finished, the exposedObject of circle is
At this point, we have reached 606 lines, you know what to do, right, go to the first level of the cache to find the loop, and then click on the circle of itstoString()
Then we go togetSingleton
method, the circle's location in the cache has changed.
It's this change that causes the next flow to change; as we continue down the line, the getSingleton method returns the circle in the secondary cache instead of the normal flow of thenull
exposedObject
not equal ≠bean
It comes to the else if branch to determine if there is a bean that depends on circle, which there is (loop), and finally to the exception branch.
if (!()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
(actualDependentBeans) +
"] 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.");
}
Where proxies are involved, what ends up in the first level of cache is the proxy of the instance, for example, circle. Let's cancel all the breakpoints and just hit a breakpoint on the circle and loop instances to get a clear picture of what's going on.
summarize
-
During Spring debugging, don't just click on the proxy's
toString
The first thing you need to do is to throw an exception, which may lead to early exposure of the object, disrupting the creation of the Spring bean and ultimately leading to an exception; throwing an exception is intuitive enough, but I'm afraid that if you don't throw an exception, you'll have all sorts of weird problems during the runtime. -
IDEA Debugging Configuration
Some versions are checked by default, which can lead to a post-debug process where we go to view an object and automatically call the object's
toString
method, which may raise some exceptions, such as the circular dependency circle pre-exposure described above -
In practice, we basically do not encounter the situation in the article, just look at the picture for fun!