For more articles, see: [www.shangsw.com]

Spring level 3 cache solves the loop dependency principle

1.1 What is Tertiary Caching

What is a cyclic dependency? We have explained it in the previous Spring source section.

3. Part 1.6 of bean loading

During the getBean process, the doGetBean is called with the following code:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); // Extract the corresponding beanName Object bean; Object sharedInstance = getSingleton(beanName); // omit return (T) bean; }Copy the code

The getSingleton(beanName) is a representation of the level 3 cache, tracing past code like this:

protected Object getSingleton(String beanName, boolean allowEarlyReference) { //1 Object singletonObject = this.singletonObjects.get(beanName); / / get the complete singleton (l1) / / level 1 cache without access to the singleton object and the object is created in situation (singletonsCurrentlyInCreation cache beanName) Start the if (singletonObject = = null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); // Get the earlier singleton object (no property injection, Start if (singletonObject == null && allowEarlyReference) {synchronized (this singletonObjects) {/ / global variable Map, concurrent access problems, write to lock singletonObject = this. SingletonObjects. Get (beanName); / / double lock checking to ensure the uniqueness of the singleton the if (singletonObject = = null) {/ / there is no / / 2 singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null) {if (singletonObject == null) { > singletonFactory = this.singletonFactories.get(beanName); If (singletonFactory! = null) { singletonObject = singletonFactory.getObject(); / / this singleton object. EarlySingletonObjects. Put (beanName singletonObject); / / deposit early singleton (no attribute injection) enclosing singletonFactories. Remove (beanName); // Remove}}}}} from the object factory cache // No early singleton was retrieved (level 2 cache) and early references are allowed to be created End} / / cache without access to the singleton object and the object is created in situation (singletonsCurrentlyInCreation cache beanName) End return singletonObject; // there is a direct return}Copy the code

Map cache is used at positions 1, 2 and 3, which correspond to level 3 cache. The corresponding three fields are as follows:

Private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** private final Map<String, ObjectFactory<? >> singletonFactories = new HashMap<>(16); Private final Map<String, private final Map<String, private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);Copy the code

Then look at the code above:

  • Note 1: In the uppermost cache, singletonObjects, the cache is the complete singleton bean, which is instantiated and initialized, and can be used directly. If not, go to 2

  • 2: In the secondary cache earlySingletonObjects, look for the bean. Level 2 caches main caches are divided into two cases, depending on whether the bean is faceted by AOP:

    • No: Saves the raw instance of a semi-finished bean with unpopulated properties
    • Yes: saves a proxyBean, that is, a proxyBean, a target bean or a semi-finished product with unpopulated properties
  • 3: If the factory object is still not found in the level 2 cache, the factory object is searched in the level 3 cache, using the factory object (factory has 3 fields, one is beanName, one is RootBeanDefinition, one is already created, To get the wrapped bean, or proxied bean

So what is a created bean with no injected properties?

For example, if a bean has 10 fields, if only after new, the object is available, the memory space has been opened, the heap has allocated space for the object, but the 10 fields are still null. That’s why it’s called a half-baked bean.

So how does Spring use this level of caching to solve the problem of loop dependencies? It starts with Spring’s bean lifecycle

1.2 Spring Bean lifecycle

Let’s take a look at the overall flow of Spring getBean:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); // Extract the corresponding beanName Object bean; Object sharedInstance = getSingleton(beanName); If (sharedInstance! = null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet -  a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); If (mbd.issingleton ()) {// Create sharedInstance = for singleton pattern getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } / / else if ignore the code} the catch (BeansException ex) {cleanupAfterBeanCreationFailure (beanName); throw ex; }} // Ignored code return (T) bean; }Copy the code

The rest of the code is commented to be ignored here. We will focus on the createBean part, which is the key function for the bean’s life cycle.

@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @nullable Object[] args)throws BeanCreationException {// Ignored code // Give BeanPostProcessors a chance to return the proxy instead of the real instance Start try {Object  bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean ! = null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } // Give BeanPostProcessors a chance to return the proxy instead of the real instance End // create and return the bean instance Start try {Object beanInstance = doCreateBean(beanName, processors) mbdToUse, args); // Ignore the code return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } // Create and return the bean instance End}Copy the code

Here there are two main parts, the first part is resolveBeforeInstantiation function part, mainly using BeanPostProcessor change the current the most original configuration bean, of course, this is not what we want to see. The second part is mainly the creation of the bean instance of the doCreateBean function.

  • Enter the doCreateBean function:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {//Bean wrappers get Start BeanWrapper instanceWrapper = null; If (MBD) isSingleton ()) {/ / singleton instanceWrapper = this. FactoryBeanInstanceCache. Remove (beanName); If (instanceWrapper == null) {if (instanceWrapper == null) {if (instanceWrapper == null) {if (instanceWrapper == null) {if (instanceWrapper == null) {if (instanceWrapper == null) { InstanceWrapper = createBeanInstance(beanName, MBD, args); } / / Bean wrapper for the End / / ignore code Boolean earlySingletonExposure = (MBD) isSingleton () && enclosing allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); If (earlySingletonExposure) {// Ignore the code // To avoid late loop dependencies, add the ObjectFactory of the created instance to the factory addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance Object exposedObject = bean; Try {// Populate the bean and inject the individual property values, where there may be properties that depend on other beans, the initial dependency bean populateBean(beanName, MBD, instanceWrapper) is recursively generated; // Call the initialization method: init-method exposedObject = initializeBean(beanName, exposedObject, MBD); } catch (Throwable ex) {if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference ! = null) {/ / earlySingletonReference detected only in a circular dependencies will not empty the if (exposedObject = = beans) {/ / if exposedObject didn't be changed in the initialization method, ExposedObject = earlySingletonReference; }}} / / ignore else if code try {registerDisposableBeanIfNecessary (beanName, bean, MBD); / / registered under the scope bean} the catch (BeanDefinitionValidationException ex)} {/ / ignore code return exposedObject; }Copy the code

The main contents of the life cycle from the start of this function:

  1. First the createBeanInstance function, which creates the bean instance, starts the bean. At this point, the bean is simply created, like new an empty constructor. Properties and so on have not been injected
  2. The second step is addSingletonFactory and getEarlyBeanReference. Of course, this part is not related to the life cycle, mainly to solve the cycle dependency
  3. The third step is the populateBean function, which performs dependency injection on individual property values and populates the properties of the empty bean above
  4. The fourth step is to call its init(if it exists) method to initialize
  5. Call the destroy method registerDisposableBeanIfNecessary (if required)

As you can see from the above code, the bean life cycle can be roughly divided into the following processes:

  1. Creating a bean instance
  2. Properties into
  3. Init initialization
  4. The destruction

After the introduction to the bean lifecycle and the general code description (I won’t read the code again here, but can read the above articles), let’s move on to see how Spring uses level 3 caching to resolve cycle dependencies.

1.3 Level 3 Cache resolves cyclic dependencies

Let’s take an example:

  • AService -> BService
  • BService -> AService

AService depends on BService, BService also depends on AService, add to getBean(AService)

Spring starts with a three-level cache, which stores not a bean but a factory object that can fetch the bean from the proxy.

In the doCreateBean above, there is the following code:

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); If (earlySingletonExposure) {// Ignore the code // To avoid late loop dependencies, add the ObjectFactory of the created instance to the factory addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }Copy the code

Here an ObjectFactory is new and stored in the third level cache as follows:

protected void addSingletonFactory(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); Synchronized (this.singletonObjects) {// lock if (! This. SingletonObjects. Either containsKey (beanName)) {/ / level cache does not exist this. SingletonFactories. Put (beanName singletonFactory); / / add cache (level 3) enclosing earlySingletonObjects. Remove (beanName); / / remove early object (the second level cache) enclosing registeredSingletons. Add (beanName); // Add registered singleton (indicating registered)}}}Copy the code

ObjectFactory (getEarlyBeanReference()) : ObjectFactory (getEarlyBeanReference()); getEarlyBeanReference();

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }Copy the code

If there is a BeanPostProcessor, then getEarlyBeanReference will fetch its object. So what does this BeanPostProcessor do in this step?

In fact, we have introduced before, in 5, AOP article, in fact, we know that AOP is woven through BeanPostProcessor, so here is mainly BeanPostProcessor AOP processing, mainly obtain proxy object proxyBean.

Therefore, we know that the level 3 cache stores the ObjectFactory of AService, and the bean stored in the ObjectFactory is the proxy object of AService.

The code goes down to the property population, finds a dependency on BService, creates a BService (while the AService is waiting to be created), BService goes to the property population using the same logic, finds a dependency on AService, goes to the getBean logic, The code to go to doGetBean looks like this:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); // Extract the corresponding beanName Object bean; Object sharedInstance = getSingleton(beanName); // omit return (T) bean; }Copy the code
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); / / get the complete singleton (l1) / / level 1 cache without access to the singleton object and the object is created in situation (singletonsCurrentlyInCreation cache beanName) Start the if (singletonObject = = null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); // Get the earlier singleton object (no property injection, Start if (singletonObject == null && allowEarlyReference) {synchronized (this singletonObjects) {/ / global variable Map, concurrent access problems, write to lock singletonObject = this. SingletonObjects. Get (beanName); / / double lock checking to ensure the uniqueness of the singleton the if (singletonObject = = null) {/ / there is no singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null) {if (singletonObject == null) { > singletonFactory = this.singletonFactories.get(beanName); If (singletonFactory! = null) { singletonObject = singletonFactory.getObject(); / / this singleton object. EarlySingletonObjects. Put (beanName singletonObject); / / deposit early singleton (no attribute injection) enclosing singletonFactories. Remove (beanName); // Remove}}}}} from the object factory cache // No early singleton was retrieved (level 2 cache) and early references are allowed to be created End} / / cache without access to the singleton object and the object is created in situation (singletonsCurrentlyInCreation cache beanName) End return singletonObject; // there is a direct return}Copy the code

Now, we already know that when the BService property populates the AService, it gets its ObjectFactory through the level 3 cache, and then getObject() gets its proxy object. This getObject is the object obtained by the anonymous inner class getEarlyBeanReference mentioned above, the proxy object AService.

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @nullable Object[] args)throws BeanCreationException {if (earlySingletonExposure) {if (earlySingletonExposure) { AddSingletonFactory (beanName, () -> getEarlyBeanReference(beanName, MBD, bean)); } // Ignore the code return exposedObject; }Copy the code

At this point, BService completes the injection of AService, and BService becomes a complete object, which is a proxy object changed by BeanPostProcessor. And put it in level 1 cache.

Continue the property injection process of AService. At this point, the BService proxy object has been created and returned. When AService injects BService, BService is actually a complete proxy object. AService injects and places a level 1 cache, which perfectly solves the problem of loop dependency.

Finally, we summarize the creation process above:

  • The AService is instantiated first, and the instantiation is exposed in the level 3 cache through the ObjectFactory semi-product
  • If BService has not been loaded, the BService will be loaded first
  • During the loading of the BService, instantiation is exposed to the level 3 cache through the ObjectFactory semi-product
  • When you populate the AService property, you can retrieve the semi-finished ObjectFactory from the level 3 cache
  • Once you get the ObjectFactory object, call objectFactory.getobject (), which eventually calls the getEarlyBeanReference() method, The main logic of this method is to return a proxyBean if the bean is proxied by AOP, and an original bean if it is not proxied.
  • The AService injected by BService is actually a semi-finished AService proxy object (the original object). The final creation of the BService,
  • Come back and continue to fill the AService properties. The completed BService will be injected and the AService will be created
  • Finally, the BService property AService is also a complete object, because they are the same object

1.4 Why level 3 Cache instead of Level 2 Cache

In fact, at this point, a lot of people are wondering why not use level 2 cache instead of level 3 cache to solve circular dependencies.

The level 2 cache can be combined in two ways:

  • SingletonObjects and earlySingletonObjects
  • SingletonObjects and singletonFactories

Either way, let’s see if the example above (AService depends on BService, and BService also depends on AService) works.

1.4.1 Combination of singletonObjects and earlySingletonObjects

Let’s go straight to the process:

  1. The AService is created and put into the early earlySingletonObjects cache, which caches semi-finished AService objects. Go to property injection BService
  2. The BService is not created and the system starts to create BService
  3. When BService is created, the attribute is injected into AService. First, the AService is obtained from the cache. The semi-finished AService can be obtained and injected into the field of BService. Remove earlySingletonObjects and add the singletonObjects cache
  4. Go through the AService property injection process, get the complete BService object, and inject it into the object. Then delete the earlySingletonObjects cache and add the singletonObjects cache
  5. The AService object that BService gets is a complete object because they are the same object

1.4.2 singletonObjects and singletonFactories

Again, let’s go straight to the process

  1. The AService is created, the ObjectFactory is put into the singletonFactories cache, and the AService’s ObjectFactory is injected into the BService
  2. The BService is not created and the system starts to create BService
  3. SingletonFactories can retrieve the AService ObjectFactory from the cache. Then call getObject to retrieve the AService. At this point, AService is also a semi-finished product, injected into the BService field, and BService completes creation and returns. Delete the singletonFactories and add the singletonObjects cache
  4. Continue the process of AService injection properties, take the completed BService object, inject it into the object, then delete the singletonFactories cache and add the singletonObjects cache
  5. The AService object that BService gets is a complete object because they are the same object

Either way, the process seems to work, and indeed it does. But we have overlooked one important feature: AOP.

1.4.3 In the case of AOP

In the case of AOP, the above combination flows differently. Let’s start with singletonObjects and singletonFactories.

We all know that AOP is through BeanPostProcessor postProcessAfterInitialization weave, and we in the presence of AOP agent, Objectfactory.getobject () finally calls the getEarlyBeanReference method:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }Copy the code

And if the presence of AOP agent, will be called SmartInstantiationAwareBeanPostProcess getEarlyBeanReference methods, we look at its parent class AbstractAutoProxyCreator method:

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}
Copy the code

We can see that the client creates the cache key, stores it in the cache, and finally calls the wrapIfNecessary method, which we seem to be familiar with. In the AOP chapter, Find AOP postProcessAfterInitialization calls this method, the specific implementation is in AbstractAutoProxyCreator:

@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean ! = null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); / / build the cache Key if (this. EarlyProxyReferences. Remove (cacheKey)! = bean) { return wrapIfNecessary(bean, beanName, cacheKey); // Encapsulate the specified bean}} return bean if appropriate to be propped; }Copy the code

Yes, they all end up with the wrapIfNecessary method, but the whole load process only goes once because there’s a cache key. Now you understand what this cache does.

The ObjectFactory is an anonymous inner class whose bean is an AOP proxy bean. The problem is that every time objectFactory.getobject () is a new object, it can’t be related to one another. The AService that results in BService injection is not a complete AService. So at this point we can reject the combination of singletonObjects and singletonFactories.

1.4.3.1 there are AOP singletonObjects and earlySingletonObjects

This is different from the above, where singletonFactories create different objects, but earlySingletonObjects doesn’t seem to have any design or code problems. Specific we can modify the Spring source debugging to know.

(1) Remove the level 3 cache and only use level 1 and level 2 cache

“SingletonFactories” (” singletonFactories “, “singletonFactories”, “singletonFactories”)

  1. Org. Springframework. Beans. Factory. Support. DefaultSingletonBeanRegistry# getSingleton (Java. Lang. String, Boolean), from the cache object, Modify it to the following form:
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); /*if (singletonObject == null) { ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } }*/ } } } } return singletonObject; // there is a direct return}Copy the code

The comment out is the level 3 cache, the Spring source code.

(2) the org. Springframework. Beans. Factory. Support. DefaultSingletonBeanRegistry# addSingleton, delete the three-level cache object. Here can be changed or not, does not affect the overall test process:

protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); // this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}Copy the code

(3) org. Springframework. Beans. Factory. Support. DefaultSingletonBeanRegistry# addSingletonFactory, add 3 cache, modified to the following:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
//                this.singletonFactories.put(beanName, singletonFactory);
//                this.earlySingletonObjects.remove(beanName);
            this.earlySingletonObjects.put(beanName, singletonFactory.getObject());
            this.registeredSingletons.add(beanName);
        }
    }
}
Copy the code

Note, here is the key, we directly to the l3 cache, but direct call singletonFactory. GetObject () to store the cache. And this ObjectFactory we know is an anonymous inner class that we get from getEarlyBeanReference. So the level 2 cache that is stored is directly the earlier unpopulated object. After the Spring source code is changed, we can test it.

First, we create two test classes with loops that rely on AService and BService as follows:

public class AService { private BService bService; private CService cService; public AService() { } public BService getbService() { return bService; } public void setbService(BService bService) { this.bService = bService; } public void test() { System.out.println("AService"); }}Copy the code
public class BService { private AService aService; public BService() { } public AService getaService() { return aService; } public void setaService(AService aService) { this.aService = aService; } public void test() { System.out.println("BService"); }}Copy the code

At this point, let’s stop doing an AOP proxy and see how it works. The configuration file is as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd "> < aop: aspectj autoproxy / > < bean id =" aService" class="org.springframework.aop.circle.bean.AService"> <property name="bService" ref="bService"/> </bean> <bean id="bService" class="org.springframework.aop.circle.bean.BService"> <property name="aService" ref="aService"/> </bean> </beans>Copy the code

The test classes are as follows:

public class CircleAopTest { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("org/springframework/circle/circleSpringAOPTest.xml"); AService aService = (AService) context.getBean("aService"); aService.test(); aService.getbService().test(); }}Copy the code

The running results are as follows:

AService
BService
Copy the code

Turns out the tests worked as we expected. Let’s add AOP to the case test and add AOP:

@Aspect
public class ServiceAOP {
   @Pointcut("execution(* org.springframework.aop.circle.bean.*.*(..))")
   public void pointcut(){}

   @Before("pointcut()")
   public void beforeTest() {
      System.out.println("beforeTest");
   }

   @After("pointcut()")
   public void afterTest() {
      System.out.println("afterTest");
   }
}
Copy the code

The above AOP crosscuts both AService and BService proxies. The configuration file needs to be changed as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd "> < aop: aspectj autoproxy / > < bean id =" aService" class="org.springframework.aop.circle.bean.AService"> <property name="bService" ref="bService"/> </bean> <bean id="bService" class="org.springframework.aop.circle.bean.BService"> <property name="aService" ref="aService"/> </bean> <bean class="org.springframework.aop.circle.aops.ServiceAOP"/> </beans>Copy the code

The test then runs the test class with the following effect:

beforeTest
AService
afterTest
beforeTest
afterTest
beforeTest
BService
afterTest
Copy the code

The effect is exactly what we need. So finally we get the proxy object of the object through the getEarlyReference method. If we remove the level 3 cache, then we can call this method manually. Why did Spring add a level 3 cache? In fact, we can guess for ourselves:

  1. The problem is calling getEarlyReference, which is not necessary in many cases because the method will not be called if there is no loop dependency. In other words, 99% of the beans are created in a circle to solve the one percent of the possibilities in the system. Efficiency doesn’t make sense
  2. Spring officially recommends constructor injection, which we know won’t solve the loop dependency problem. Isn’t that a contradiction of Spring? So assuming that Spring doesn’t want us to use Spring’s cyclic dependencies, it’s much less serious to be able to break a problem when the project starts than not knowing when it will break after the project runs.
  3. Facilitate subsequent extensions

So, functionally, it is perfectly possible to use level 2 caches (singletonObjects and earlySingletonObjects) to solve the problem of loop dependency, but for efficiency and extensibility, Spring uses level 3 caches to solve this problem.