From the perspective of source code, I will take you to study what is L3 cache

  • Java development, you will always encounter the problem of three-level cache.
  • After reading many articles, I don't feel very thorough. I plan to write a detailed understanding of L3 cache. There are pictures and texts. There are also textual generalizations. Those who can't stand moving pictures can see the text summary

Get to the point:

  • In the development process, we will encounter the problem of circular dependency. Just like the picture below
  • Spring has designed a three-level cache to solve the above dependency problems

First, we need to know what is stored in the L3 cache

The finished objects are stored in the L1 cache. The instantiation and initialization are completed. The objects used in our application are the objects in the L1 cache

The second level cache stores semi-finished products to solve the problem of circular dependency in the process of object creation

Objectfactory <? > is stored in the L3 cache lambda expression of type, which is used to deal with the circular dependency problem when AOP exists

Order of Spring L3 cache

The order of the L3 cache is derived from the query in order, regardless of the order of definition in the class

So the first level cache is singleton objects, the second level cache is earlySingletonObjects, and the third level cache is singleton factories

Spring has three injection methods: construction method injection, setter method injection and interface injection

The interface injection method is too flexible and easy to use, so it has not been widely used. As we all know, it's good to say so. Don't make fine deduction

The method of constructing method injection completes instantiation and initialization together. It can quickly create an object that can be used directly, but it can't deal with the problem of circular dependency. Just understand

The way of setter method injection is to call the setter method of the object to complete the attribute assignment through reflection after the object instantiation is completed. It can deal with the problem of circular dependency. It is the cornerstone of the following text and must be familiar with it

Spring source code analysis

  • The following will track the source code from several different situations

1. No dependency, AOP

The code is very simple: spring-no-dependence

  • The logic in the figure above is ClassPathXmlApplicationContext#refresh
  • -> this.finishBeanFactoryInitialization(beanFactory)
  • -> beanFactory.preInstantiateSingletons()
  • > isFactoryBean to judge bean, then call getBean method.
  • Next, call the doGetBean method
  • The logic in the figure above is that DefaultSingletonBeanRegistry#getSingleton determines whether there is a cache. If not, create a Bean
  • Can be observed
 Map<String, Object> singletonObjects               nothing simpleBean 
 Map<String, Object> earlySingletonObjects          nothing simpleBean 
 Map<String, ObjectFactory<?>> singletonFactories   nothing simpleBean 
 Set<String> singletonsCurrentlyInCreation          have simpleBean 
  • This indicates that the bean is being created
  • We then follow from the createBean
  • The key code is in doCreateBean, and several key method calls are worth following
 Map<String, Object> singletonObjects               Stored simpleBean Proxy object for
 Map<String, Object> earlySingletonObjects          Not from beginning to end simpleBean object
 Map<String, ObjectFactory<?>> singletonFactories   After saving for a while, the data is deleted immediately and has not been used  
 
 // The methods described below are under the AbstractAutowireCapableBeanFactory class
 // createBeanInstance instantiates the object through reflection to obtain the semi-finished object. Allocate memory space to even semi-finished products
 // populateBean fills in the semi-finished product attribute. If there are dependent objects, they are introduced here
 // initializeBean initializes a semi-finished object
 // Applybeanpostprocessorsafterinitialization is the post-processing of beanpostprocessor, and the proxy object replacement of AOP is completed here  
  • At this time: the proxy object is created after the object instantiation and initialization are completed. It is to create a proxy object for a finished object
  • Therefore, in the case of "no dependency, AOP": only the first level cache is enough, and the other two caches can complete the object without it

2. Circular dependency, no AOP

The code is still very simple: spring-circle-simple At this time, the two classes that the Loop depends on are Circle and Loop

The object creation process is basically the same as the previous one, except that there are more circular dependencies and less AOP, so we focus on the populateBean and initializeBean methods

The Circle object is created first, so let's start with the populateBean that created it. Before we start, let's take a look at the data in the L3 cache

 Map<String, Object> singletonObjects               nothing circle Neither loop object
 Map<String, Object> earlySingletonObjects          nothing circle Neither loop object
 Map<String, ObjectFactory<?>> singletonFactories   only cicle of lambda
 Set<String> singletonsCurrentlyInCreation          only circle 
  • Let's start with populateBean, which completes the filling of attributes, which is related to circular dependency. We must look carefully and follow it carefully

When filling the loop attribute of the circle object, go to the Spring container to find the loop object. If it is not found, create it, and then come to the familiar createBean

At this time, the data in the L3 cache does not change, but there are multiple loop IDS in set < string > singletonscurrentlyincreation, and the loop is being created

After the loop instantiation is completed, fill in its attribute circle, get the circle object in Spring, and then come to the familiar doGetBean

At this time, there are no circle s and loop s in the L1 and L2 caches (singletonObjects``earlySingletonObjects), but there are these two in the L3 cache

When obtaining a circle through getSingleton, the L3 cache calls getEarlyBeanReference, but since there is no AOP, getEarlyBeanReference directly returns an ordinary semi-finished circle

The semi-finished circle is then placed in the L2 cache, returned, and populated into the loop object

At this time, the loop object is a finished object; Then return the loop object and fill it in the circle object, as shown below

We found that the finished loop was directly put into the L1 cache, and the L2 cache never had a loop. Although the L3 cache saved the loop, it was directly remove d without using it

At this time, I believe everyone can think of the data in the cache

 Map<String, Object> singletonObjects               nothing circle have loop object
 Map<String, Object> earlySingletonObjects          have circle nothing loop object
 Map<String, ObjectFactory<?>> singletonFactories   nothing circle Neither loop
 Set<String> singletonsCurrentlyInCreation          have circle nothing loop because loop Created
  • When the loop object finishes creating beans, it will call defaultsingletonbeanregistry #getsingleton - > defaultsingletonbeanregistry #addsingleton
  • Move the data object to the L1 cache. The circle of the L2 cache is deleted when it is useless. Only the circle exists, and the data of the L3 cache is called. Return the circle of the semi-finished product to the loop object
  • Therefore, in the case of circular dependency without AOP, a cache can be reduced, and only two-level cache is enough

Summary: (circular dependency, no AOP)

  • The above steps can be summarized as follows:
  • The first step. doCreateBean creates a circle. The creation steps are as follows:
  • Process of circle:
- `AbstractBeanFactory#doGetBean ` get bean
- -> `AbstractAutowireCapableBeanFactory#createBean ` create bean
- -> `AbstractAutowireCapableBeanFactory#doCreateBean ` start creating bean
- -> `AbstractAutowireCapableBeanFactory#addSingletonFactory ` adds a lambda of the bean to the third level cache to singletonFactories
- -> `AbstractAutowireCapableBeanFactory#populateBean ` populate bean
- -> `AbstractAutowireCapableBeanFactory#applyPropertyValues ` it is checked that there is one to add for filling
- -> `BeanDefinitionValueResolver#resolveValueIfNecessary ` attention! Get the loop object from this location
```java
- Let's look at the next three caches Map Storage of
```java 
 Map<String, Object> singletonObjects               nothing circle Neither loop object
 Map<String, Object> earlySingletonObjects          nothing circle Neither loop object
 Map<String, ObjectFactory<?>> singletonFactories   have circle Neither loop object
 Set<String> singletonsCurrentlyInCreation          have circle 
  • Step 2. Then get loop Bean will repeat the above steps
- `AbstractBeanFactory#doGetBean ` get bean
- -> `AbstractAutowireCapableBeanFactory#createBean ` create bean
- -> `AbstractAutowireCapableBeanFactory#doCreateBean ` start creating bean
- -> `AbstractAutowireCapableBeanFactory#addSingletonFactory ` adds a lambda of the bean to the third level cache to singletonFactories
- -> `AbstractAutowireCapableBeanFactory#populateBean ` populate bean
- -> `AbstractAutowireCapableBeanFactory#applyPropertyValues ` it is checked that there is one to add for filling
- -> `BeanDefinitionValueResolver#resolveValueIfNecessary ` attention! This position has been changed. Get the circle object
  • At the breakpoint, we observe the storage of the three cached maps
 Map<String, Object> singletonObjects               nothing circle Neither loop object
 Map<String, Object> earlySingletonObjects          nothing circle Neither loop object
 Map<String, ObjectFactory<?>> singletonFactories   have circle have loop object
 Set<String> singletonsCurrentlyInCreation          have circle have loop Indicates that both objects are being created 
  • Here comes the key point:
  • The third step is equivalent to the program entering the AbstractBeanFactory#doGetBean of the circle for the second time
- `AbstractBeanFactory#doGetBean ` get circle for the second time
- `AbstractBeanFactory#getSingleton(beanName) ` get cache of Bean
- `DefaultSingletonBeanRegistry#getSingleton(beanName, true) ` get cache of Bean
- `DefaultSingletonBeanRegistry#Issingletoncurrentyincreation (beanname) ` key!! Determine whether the bean with the name circle is in the creation process
- `this.singletonFactories.get(beanName)`                                   Get this circle of lambda Create function
- `singletonFactory.getObject()`                                            The calling function gets a semi-finished object. that is loop Still empty circle object
- `this.earlySingletonObjects.put(beanName, singletonObject)`               Add objects to the L2 cache   earlySingletonObjects Added object
// Attached, only earlySingletonObjects adds a circle object, and other map s remain unchanged. And the singleton factories of loop are not used
  • Then it returns the circle to the loop for attribute filling
  • When the loop is created, clear the loop (earlySingletonObjects, singletonFactories, singletonsCurrentlyInCreation). Loop add objects to singletonObjects
  • Return the created loop to the filling attribute process of the circle
  • After filling. Clear in (earlySingletonObjects, singletonFactories, singletonsCurrentlyInCreation). Add a circle object to singletonObjects
  • Note: even if the circle is only a semi-finished product, it is unique in the bean. As long as the circle attribute is filled with loop, it is in the singleton cache of loop. There will be circular objects with circular dependencies
  • In fact, in the whole process, the circle will enter the L2 cache. But it didn't work. It was remove d
  • Loop has never appeared in L2 cache. Because it will not enter the doGetBean process of loop twice. The third level cache data of loop was deleted without being used.

2. Circular dependency with AOP

The code is still very simple: spring-circle-aop , AOP is added on the basis of circular dependency

There is more AOP than the previous case. Let's take a look at the difference in the creation process of objects; Similarly, create a Circle first, and then create a Loop

The creation process is basically the same as that in the previous case, but there are only a few differences. When it is compared with the source code, I will pause on these differences, and others will be skipped. You should look carefully

Instantiate the circle, then fill in the loop attribute of the semi-finished circle, go to the Spring container to get the loop object, and find no

Instantiate the loop, then fill in the attribute circle of the semi-finished loop, and get the circle object from the Spring container

This process is the same as the previous case, so we skip it directly. At this time, the data in the L3 cache is as follows:

 Map<String, Object> singletonObjects               nothing circle Neither loop object
 Map<String, Object> earlySingletonObjects          nothing circle Neither loop object
 Map<String, ObjectFactory<?>> singletonFactories   have circle have loop object
 Set<String> singletonsCurrentlyInCreation          have circle have loop Indicates that both objects are being created 

We found that when getting a circle from the third level cache, we called getEarlyBeanReference to create a proxy object for the semi-finished circle

The proxy object of the semi-finished product circle is put into the second level cache, and the return of the proxy object is assigned to the circle attribute of the semi-finished product loop

Note: at this time, the loop is initialized, but the proxy object of the semi-finished circle is created in advance

The initialization of loop has not been completed yet. Let's move on to another key point. Take a closer look

The initialization of the semi-finished loop is completed in the initializeBean method, and the proxy object of the finished loop is created at the end

After the loop proxy object is created, it will be put into the first level cache (the loop in the third level cache will be removed, and there will be no loop in the second level cache from beginning to end)

Then, the loop proxy object is returned and assigned to the attribute loop of the semi-finished circle, and then the initializeBean of the semi-finished circle is initialized

Because the proxy object of circle has been generated (in the second level cache), there is no need to generate the proxy object; Move the circle proxy object from the second level cache to the first level cache and return the proxy object

At this time, the data in each level of cache is as follows (ordinary circle and loop objects are in the target of their proxy objects)

 Map<String, Object> singletonObjects               have circle Proxy objects are loop Proxy object
 Map<String, Object> earlySingletonObjects          nothing circle nothing loop object
 Map<String, ObjectFactory<?>> singletonFactories   nothing circle nothing loop object
 Set<String> singletonsCurrentlyInCreation          nothing circle nothing loop 

In this case, we review the sense of existence of caches at all levels. The first level cache still has a full sense of existence, the second level cache has a sense of existence, and the third level cache has a very strong sense of existence

The third level cache creates the circle proxy object in advance. If it is not created in advance, it can only assign the attribute circle of the loop object as a semi-finished circle. Then the circle object in the loop object has no AOP enhancement

The second level cache is used to store the circle agent and solve the circular dependency; Maybe it is not obvious enough in this example, because the dependency is relatively simple, and you can feel it if the dependency is a little more complex The first level cache stores exposed objects, which may be proxy objects or ordinary objects

Therefore, in this case, there should be no less L3 cache

Summary: (2. Circular dependency with AOP)

  • Basically consistent with the generalization: (circular dependency, no AOP)
  • Change in step 3:
- `AbstractBeanFactory#doGetBean ` get circle for the second time
- `AbstractBeanFactory#getSingleton(beanName) ` get cache of Bean
- `DefaultSingletonBeanRegistry#getSingleton(beanName, true) ` get cache of Bean
- `DefaultSingletonBeanRegistry#Issingletoncurrentyincreation (beanname) ` judge whether the bean with the name circle is in the creation process
- `this.singletonFactories.get(beanName)`                                   Get this circle of lambda Create function 
- `singletonFactory.getObject()`                                            The calling function gets a semi-finished object. (note!! yes AOP The surrounding object creates a proxy object at this location and passes the proxy object through AbstractAutoProxyCreator#getEarlyBeanReference is synchronized to the AOP creation class (for later use), that is, the circle object whose loop is still empty
- `this.earlySingletonObjects.put(beanName, singletonObject)`               Add objects to the L2 cache   earlySingletonObjects Added object
// Attached, only earlySingletonObjects adds a circle object, and other map s remain unchanged. 
  • Then the loop is created.
  • Then, after the circle is filled.
- -> `AbstractAutowireCapableBeanFactory#populateBean ` after filling the bean
- -> `AbstractAutowireCapableBeanFactory#initializeBean ` initializes a circle
- -> `AbstractAutowireCapableBeanFactory#Applybeanpostprocessorsafterinitialization ` post notification of the bean. This location will wrap the bean AOP and return the proxy object
- Because it's above loop obtain circle A proxy object has already been created at the time of. So this aop You can't create a new proxy class. Otherwise, it's inconsistent
- Then look down
- -> `AbstractAutoProxyCreator#postProcessAfterInitialization ` create proxy object
- -> `if (this.earlyProxyReferences.remove(cacheKey) != bean)`        At this time, L2 cache comes in handy. here Determine whether there is already a proxy class. If there is a proxy class, the proxy class object will not be created.
// In this way, the proxy of the circle will not be created repeatedly. L2 caching also comes in handy

4. Circular dependency + AOP + delete L3 cache

In the case of no dependency and AOP, we know that the generation of AOP proxy object is created after the creation of finished object, which is also the design principle of Spring. The creation of proxy object shall be delayed as much as possible

In the case of circular dependency + AOP, the generation of circle proxy object is advanced because its AOP function must be guaranteed, but the generation of loop proxy object still follows the principles of Spring

If we break this principle and advance the creation logic of proxy objects, can we use only two-level cache instead of three-level cache?

The code is still simple: spring-circle-custom , only minor changes have been made to the source code of Spring, as follows

The third level cache is removed, and the creation logic of the proxy object is advanced after instantiation and before initialization;

summary

1. Functions of L3 cache

The first level cache stores exposed objects, which are needed by our applications

The second level cache is used to deal with the creation of circular dependent objects. It stores semi-finished objects or proxy objects of semi-finished objects

The role of the third level cache is to deal with the creation of objects with AOP + circular dependency, and can create proxy objects in advance

2. Why does Spring introduce the third level cache

Strictly speaking, the third level cache is not indispensable because proxy objects can be created in advance

Creating proxy objects in advance will only save a lot of memory space and will not improve performance, but it will break the design principles of Spring

Spring's design principle is to ensure that after the creation of ordinary objects, they can be regenerated into their AOP proxy (delaying the generation of proxy objects as much as possible)

Therefore, Spring uses the third-level cache, which not only maintains the design principles, but also handles circular dependencies; It is acceptable to sacrifice so much memory space

Posted on Sun, 05 Dec 2021 02:43:03 -0500 by michaeru