Location>code7788 >text

When a Spring circular dependency bumps into Aysnc, a BeanCurrentlyInCreationException appears during debugging, which is kind of fun!

Popularity:448 ℃/2024-08-12 08:44:08

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

断点condition

To start debugging, to make it easier to see what's in the three-level cache, we add threewatch

添加watch

Add all three levels of cache

三级缓存watch

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

第一级缓存loop 点击toString

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

第二级缓存非空_有circle

It's amazing that the second level cache has elements, so the third level cache of thecircle Does it still exist?

第三级缓存_circle没了

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?

  1. Tapped circle's toString()
  2. 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.

DefaultSingletonBeanRegistry

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)circleand then click on itstoString()), after canceling all breakpointsF9BeanCurrentlyInCreationException 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

circle代理对象存入三级缓存

Then fill in the properties of the Circle semi-finished objectLoop loopThe third level of caching creates the Loop proxy in advance (it's not used, so you can just remove it later).

Loop代理对象存入第三级缓存

At this point we look at the stack frame of the current thread

创建loop时的栈帧

Then fill in the properties of the Loop semi-finished objectCircle circleAt 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.

loop的circle属性

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)

loop实例加入第一级缓存

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

loop填充到circle中

When the initialization of circle is finished, the exposedObject of circle is

circle曝光对象

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()

点击circle toString

Then we go togetSingleton method, the circle's location in the cache has changed.

circle来到第二级缓存

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

circle_问题关键点

exposedObject not equal ≠beanIt 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.

circle是代理对象而loop不是

summarize

  1. During Spring debugging, don't just click on the proxy'stoStringThe 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.

  2. IDEA Debugging Configuration

    debug对象toString默认调用开关

    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'stoString method, which may raise some exceptions, such as the circular dependency circle pre-exposure described above

  3. In practice, we basically do not encounter the situation in the article, just look at the picture for fun!

    20240128194820