How to solve Spring circular dependency through three-level cache

The following content is based on Spring6.0.4.

This is actually a very high-frequency interview question, and I have always wanted to talk about this topic with you in detail. There are many articles on this topic on the Internet, but I always feel that it is still a bit difficult to explain this question clearly. Today I will come to Give it a try and see if you can sort out this problem with your friends.

1. Circular dependencies

1.1 What is a circular dependency

First, what is a circular dependency? This is actually easy to understand, that is, two beans depend on each other, similar to the following:

@Service
public class AService {
    @Autowired
    BService bService;
}
@Service
public class BService {
    @Autowired
    AService aService;
}

AService and BService depend on each other:

This should be well understood.

1.2 Types of circular dependencies

Generally speaking, there are three different forms of circular dependencies, and the above section 1.1 is one of them.

The other two are the three dependencies, as shown in the figure below:

This kind of circular dependency is generally hidden deeply and is not easy to detect.

There is also self-reliance, as shown below:

Generally speaking, if there are circular dependencies in our code, it means that there may be problems in the design process of our code, and we should try to avoid the occurrence of circular dependencies. However, once a circular dependency occurs, Spring will handle it for us by default. Of course, this does not mean that the code of circular dependency is fine. In fact, in the latest version of Spring, the circular dependency is additionally enabled. If there is no additional configuration, an error will be reported directly if the circular dependency occurs.
In addition, Spring cannot handle all circular dependencies, Song Ge will analyze with you later.

2. Circular dependency solution ideas

2.1 Solutions

So how to solve the circular dependency? In fact, it's very simple, just add a cache to China Plus, let's take a look at the following picture:

We have introduced a cache pool here.

When we need to create an instance of AService, we will first create an original AService through Java reflection. This original AService can be simply understood as an AService that has just been new (actually just created through reflection) and has not set any attributes. , we store this AService in a buffer pool first.

Next, we need to set values ​​for the properties of AService, and at the same time, we need to deal with the dependencies of AService. At this time, we found that AService depends on BService, so we created a BService object. When we created BService, we found that BService depends on AService, then at this time First take out AService from the buffer pool and use it first, and then continue the subsequent process of BService creation until the BService is created, then assign it to AService, and then both AService and BService are created.

Some friends may say that the AService obtained by BService from the buffer pool is a semi-finished product, not the real final AService, but friends, we should know that our Java is passed by reference (it can also be considered as passed by value, but This value is the memory address), what BService got at that time was the reference of AService, to put it bluntly, it was just a memory address, and the AService was found according to this address, so, if AService is created later, the AService obtained by BService is complete The AService has.

Then the cache pool mentioned above has a special name in the Spring container, which is called earlySingletonObjects. Without going through the full life cycle, the properties of the Bean may not have been set, and the dependencies required by the Bean have not yet been injected. The other two levels of cache are:

  • singletonObjects: This is the first-level cache. The first-level cache stores all Beans that have gone through a complete life cycle, that is, a Bean has experienced everything from creation, attribute assignment, and execution of various processors. Stored in singletonObjects, when we need to get a Bean, we will first go to the first-level cache to find it, and when there is no one in the first-level cache, we will consider going to the second-level cache.
  • singletonFactories: This is the third level cache. In the first-level cache and the second-level cache, the key of the cache is beanName, and the value of the cache is a Bean object, but in the third-level cache, the value of the cache is a Lambda expression, through which the target can be created A proxy object for the object.

Some friends may find it strange that according to the above introduction, the first-level cache and the second-level cache are enough to solve the circular dependency, why is there a third-level cache? Then you have to consider the situation of AOP!

2.2 What to do if there is AOP

What I introduced to you above is ordinary Bean creation, so there is really no problem. But there is another very important capability in Spring, that is AOP.

Having said that, I have to talk to my friends about the creation process of AOP in Spring.

Normally, we first obtain a Bean instance through reflection, and then fill in the attributes for the Bean. After the attributes are filled, the next step is to execute various BeanPostProcessors. If there are methods in the Bean that need to be proxied, the system will The corresponding post-processor will be automatically configured. Songge will give a simple example. Suppose I have the following Service:

@Service
public class UserService {

    @Async
    public void hello() {
        System.out.println("hello>>>"+Thread.currentThread().getName());
    }
}

Then the system will automatically provide a
processor named AsyncAnnotationBeanPostProcessor, in this processor, the system will generate a proxy UserService object, and use this object to replace the original UserService.

So friends, what you need to figure out is that the original UserService and the newly generated proxy UserService are two different objects, occupying two different memory addresses! ! !

Let's look back at the picture below:

If AService is to generate a proxy object in the end, then the original AService is actually stored in the cache pool, because it has not yet reached the step of processing AOP (you must first assign values ​​​​to each attribute, and then AOP processing), this As a result, the AService obtained by BService from the cache pool is the original AService. After the BService is created, the attribute assignment of AService is completed. Then, in the subsequent creation process of AService, AService will become a proxy object, no The AService in the cache pool is broken, and eventually the AService that the BService depends on is not the same as the AService that is finally created.

To solve this problem, Spring introduces a three-level cache singletonFactories.

The working mechanism of singletonFactories is as follows (assuming AService is ultimately a proxy object):

When we create an AService, after the original AService is created through reflection, first judge whether the current Bean exists in the current level-1 cache, if not, then:

  1. First add a record to the third-level cache. The key of the record is the beanName of the current Bean, and the value is a Lambda expression ObjectFactory. By executing this Lambda, a proxy object can be generated for the current AService.
  2. Then, if the current AService Bean exists in the second-level cache, remove it.

Now continue to assign values ​​to the attributes of AService. It turns out that AService needs BService, and then creates BService. When creating BService, it is found that BService needs AService, so first go to the first-level cache to find out whether there is AService. If so, Just use it, if not, go to the second-level cache to find out whether there is AService, if there is, use it, if not, go to the third-level cache to find the ObjectFactory, and then execute the getObject method here, this method is in the process of execution , it will judge whether a proxy object needs to be generated, if necessary, generate a proxy object and return it, if it does not need to generate a proxy object, then return the original object. Finally, store the obtained object in the second-level cache for next use, and delete the corresponding data in the third-level cache. In this way, the BService that AService depends on is created.

Next, continue to improve AService and execute various post-processors. At this time, some post-processors want to generate proxy objects for AService, and find that AService is already a proxy object, so there is no need to generate it, and directly use the existing The proxy object can be used instead of AService.

So far, both AService and BService are done.

In essence, singletonFactories advances the AOP process.

3. Summary

In general, Spring solves circular dependencies by grasping two key points:

  • Early exposure : When the newly created object has not been assigned any value, it will be exposed and placed in the cache for early reference by other beans (secondary cache).
  • AOP in advance : when A depends on B, check whether a circular dependency has occurred (the way to check is to mark the A being created, and then B needs A, and when B creates A, it finds that A is being created, which means it has happened Circular dependency), if a circular dependency occurs, AOP processing will be performed in advance, and it will be used after processing (three-level cache).
Originally, the process of AOP is to process AOP (AbstractAutoProxyCreator) by various post-processors (AbstractAutoProxyCreator) after the attributes are assigned values. Time, no longer do AOP processing.

However, it should be noted that the three-level cache cannot solve all circular dependencies, so we will continue to discuss this in detail later in the article.

Guess you like

Origin blog.csdn.net/mxt51220/article/details/131785210
Recommended