preface

While researching “How Spring solves loop dependencies,” I learned that Spring solves loop dependencies with the help of tertiary caching.

Also left in doubt in the previous section:

  1. Why use level 3 caching for circular dependencies? Instead of using a level 2 cache?
  2. Does AOP dynamic proxy have any impact on circular dependencies?

This article is also based on the above content.

My notes are getting organized. They might have been a little messy before.

Step by step, what is cyclic dependency?

Let’s start with a brief review of the Bean creation process. You can also read the article “Singleton Bean creation” directly.

However, considering that it would be time-consuming to read the previous article and Debug before reading this article, I will briefly summarize the content of the previous article in the first part of this article, which is also equivalent to summarizing the knowledge I have learned.

Let’s review the concept of a three-level cache.

SingletonObjects: level 1 cache, store singletonObjects, Bean already instantiated, initialized.

EarlySingletonObjects: a secondary cache that stores singletonObjects. This Bean is instantiated and has not been initialized.

SingletonFactories: A three-level cache that stores singletonFactories.

The Bean creation process

@Service
public class CircularServiceA {
    private String fieldA = "The field is A";
}
Copy the code

Through the above process, we can see that the Spring in the process of creating Bean is focused on the following three steps: in the AbstractAutowireCapableBeanFactory

  1. Instantiate createBeanInstance:It instantiates and assigns a value to the Bean, as in the examplefieldAThe field will be assigned here.
  2. Attribute injection populateBean: This can be interpreted as assigning values to the attributes in the Bean. (Will depend on other beans)
  3. InitializeBean: The post-processor that performs the initialization and Bean.

Instantiate assignment source code can be read:

BeanUtils.instantiateClass(constructorToUse)

What if you want to rely on other beans?

What if CircularServiceA relies on other beans?

@Service
public class CircularServiceA {

    private String fieldA = "The field is A";

    @Autowired
    private CircularServiceB circularServiceB;

}
@Service
public class CircularServiceB {}Copy the code

When A depends on B, the createBeanInstance step does not assign an attribute to B.

Instead, look for the dependency here in the populatedBean and create B.

The creation process under cyclic dependencies

The loop dependency scenario, which was explained in the previous article, is illustrated here.

@Service
public class CircularServiceA {

    private String fieldA = "The field is A";

    @Autowired
    private CircularServiceB circularServiceB;

}
@Service
public class CircularServiceB {
    @Autowired
    private CircularServiceA circularServiceA;
}
Copy the code

In the case of A and B cyclic dependencies:

B When the populatedBean looks for dependency A, it does not get A from the level 1 cache, but finds that A is being created.

At this point, the singletonFactory that fetched A from the tertiary cache calls the factory method, creating an earlier reference to getEarlyBeanReference A and returning it.

B references A, B can be initialized, and then A can be initialized as well.

Whether the second level cache resolves circular dependencies

In fact, it is possible to remove the second level cache and return the instance of A directly when B tries to obtain A.

The answer is: yes!

But why level 3 caching?

A lot of information on the Internet is related to dynamic agents, then continue to analyze from the aspect of dynamic agents.

Dynamic proxy scenario

Add the @EnableAspectJAutoProxy annotation to the JavaConfig (configuration class), turn on AOP, and take a step-by-step look at the effects of dynamic proxies on circular dependencies with Debug.

The process of Bean creation under dynamic proxy

@Service
public class CircularServiceA {
    private String fieldA = "The field is A";

    public void methodA(a) {

        System.out.println("Method A execution"); }}@Aspect
@Component
public class AspectA {

    @Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())")
    public void beforeA(a) {
        System.out.println("BeforeA execution"); }}Copy the code

If only USER A exists, add A section to user A and start debugging.

The previous process is the same, and differences begin to appear in the initializeBean.

This step involves initializing the Bean and executing the Bean’s post-processor.

One of the processors is: AnnotationAwareAspectJAutoProxyCreator is added annotations aspect, will jump to AbstractAutoProxyCreator postProcessAfterInitialization method of a class

As shown in the figure, the wrapIfNecessary method determines whether the proxy condition is met and returns a proxy object if so, or the current Bean if not.

Subsequent calls to getProxy, createAopProxy, and so on, culminate in the next section.

This is the end of the execution, and the AOP proxy concerns will not be examined.

All the way through until the initializeBean execution ends.

A is replaced with A proxy object.

So the doCreateBean returns, and the subsequent ones placed in the level 1 cache, are proxy objects.

Dynamic proxies with cyclic dependencies

Turn the loop dependency on this time:

@Service
public class CircularServiceA {

    private String fieldA = "The field is A";

    @Autowired
    private CircularServiceB circularServiceB;

    public void methodA(a) {

        System.out.println("Method A execution"); }}@Aspect
@Component
public class AspectA {

    @Before("execution(public void com.liuzhihang.circular.CircularServiceA.methodA())")
    public void beforeA(a) {

        System.out.println("BeforeA execution"); }}@Service
public class CircularServiceB {

    @Autowired
    private CircularServiceA circularServiceA;

    public void methodB(a) {}}@Aspect
@Component
public class AspectB {
    
    @Before("execution(public void com.liuzhihang.circular.CircularServiceB.methodB())")
    public void beforeB(a) {

        System.out.println("BeforeB execution"); }}Copy the code

Start Debug, which is the same as normal. The only difference is that when you create B, you need to fetch A from the level 3 cache.

At this point in the getSingleton method will be called: singletonObject = singletonFactory. GetObject ();

Sometimes more doubts singletonFactory. GetObject () call is where?

So this one calls getEarlyBeanReference and starts iterating through the BeanPostProcessor.

See wrapIfNecessary to understand! This gets a proxy object.

In other words, what is returned and put into the level 2 cache is A proxy object of A.

And then B is created!

It’s time to initialize and execute the post-processor at A! Because have A agent, so this part will also perform A to postProcessAfterInitialization!

However, before executing wrapIfNecessary, it determines whether the proxy object cache has an A.

this.earlyProxyReferences.remove(cacheKey) ! = bean

But this gets the proxy object of A. It must be false. So it doesn’t regenerate as A proxy object of A once.

conclusion

As you can see, the difference between cyclic dependency and no proxy is as follows:

singletonObject = singletonFactory.getObject();

When assigning A to B in case of cyclic dependency:

  1. No proxy: getObject returns the original Bean directly
  2. There are proxies: getObject returns a proxy object

And then put it all in level 2 cache.

Why level 3 caching?

  1. Suppose we get rid of the tertiary cache

With the third-level cache removed, the Bean creates earlySingletonObjects directly, which seems like a good idea.

EarlySingletonObjects simply places the proxy object in earlySingletonObjects if there are proxies available.

But can lead to a problem: in the instantiation phase have to implement the post processor, determine AnnotationAwareAspectJAutoProxyCreator and create proxy objects.

If you think about it this way, does it have an impact on the Bean’s life cycle?

Similarly, to create the first singletonFactory advantage is: instantiation, it really needs to reuse singletonFactory. GetObject () to obtain the Bean or the Bean’s agent. This is equivalent to delayed instantiation.

  1. Suppose we get rid of the second-level cache

If you remove the second level cache, you may need to directly in singletonFactory. GetObject (phase) after initialization, and on the level of cache.

There’s A scenario where BOTH B and C depend on A.

Want to know. In the case of agent singletonFactory getObject () to obtain the proxy objects.

And multiple calls singletonFactory. GetObject () returns the proxy object is different, can lead to B and C depends on the different A.

What if I get B and then I put it in level 1 cache, and then I get C?

😳…

The first level cache is for beans that have already been initialized. Remember that A depends on B and C, and A has not been initialized yet.

summary

There are many scenarios for loop dependencies. This article is just about debugging the relationship between loop dependencies and AOP, and why level 3 caching is used.

Of course, what was Spring like when it was designed? How did you get to where you are?

Certainly can’t slowly go to study, so can only with the current version, to guess the author’s intention.

Deficiencies, a lot of correction.

Related to recommend

  • How does Spring address loop dependencies?
  • Spring source code learning 16: singleton Bean creation
  • Spring source study 15: finishBeanFactoryInitialization (key)