Spring's handling of circular dependencies in singleton mode

1: write in front

This article analyzes on the basis that spring creates beans in different ways . As a supplement, it analyzes in detail how spring solves circular dependencies in singleton mode. For what circular dependencies are, you can refer to here , because spring only solves singleton mode. Circular dependencies based on attribute references, so this paper analyzes circular dependencies in this way (Voice-over: There is nothing else to analyze, because it is abnormal directly!).

2: Preparations

2.1: Prepare 2 classes

public  class  MyA  { 
    private MyB myB ;

    public MyB getMyB ( )  { 
        return myB ; 
    }

    public  void  setMyB ( MyB myB )  { 
        this . myB = myB ; 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
public  class  MyB  { 
    private MyA myA ;

    public MyA getMyA ( )  { 
        return myA ; 
    }

    public  void  setMyA ( MyA myA )  { 
        this . myA = myA ; 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.2: xml

<?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: ok = " http://dongshi.daddy.com/schema/ok "

       xsi: schemaLocation = " http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://dongshi.daddy.com/schema/ok
           http://dongshi.daddy.com/schema/ok/dongshidaddy-1.0.xsd " > 
    < bean  class = " yudaosourcecode.singletonrecycleref.MyA "
           id = " myA "
           lazy-init = " true "
           scope = " singleton " > 
        < property  name = " myB "  ref = " myB " /> 
    </ bean >

    < bean  class = " yudaosourcecode.singletonrecycleref.MyB "
           id = " myB "
           lazy-init = " true "
           scope = " singleton " > 
        < property  name = " myA "  ref = " myA " /> 
    </ bean > 
</ beans >
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Note that thescope=singleton,;lazy-init=true, the purpose of setting lazy loading is to initialize when the bean is obtained, which is convenient for us to debug.

2.3: Test the code

@Test 
public  void  testsingletonrecycleref ( )  {
    ClassPathXmlApplicationContext ac
            =  new  ClassPathXmlApplicationContext ( "testsingletonrecycleref.xml" ) ; 
    ac . getBean ( "myA" ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3: getSingleton

Location:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
testing codeac.getBean("myA");breakpoint debugging.

3.1: How to debug

Run the test program in debug mode, and debug the execution to the codeorg.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean), as follows:
insert image description here
At this time, the result of the if judgment is as follows:
insert image description here
What is to be loaded at this time ismyA,ThenF8will be loaded becausemyBwill enter again, and the if judgment is still false at this time, because the name of the singleton bean being created at this time is onlymyA, as shown below:
insert image description here
then againF8At this time, because it has to be loaded againmyAWill be executed again here, the name collection of the singleton bean being created at this time hasmyA,myBTwo, myA is currently loaded, and myA is already being created, which constitutes a circular dependency, as follows:
insert image description here
If you need to debug the program, you can debug it in this execution state.

3.2: Source code

The source code is as follows:

protected Object getSingleton ( String beanName ,  boolean allowEarlyReference )  { 
	// <2021-03-25 16:19> 
	Object singletonObject =  this . singletonObjects . get ( beanName ) ; 
	// <2021-03-25 16:20> 
	// if Is there a circular dependency 
	if  ( singletonObject == null &&  isSingletonCurrentlyInCreation ( beanName ) )  { 
		// lock on singletonObjects 
		synchronized  ( this .singletonObjects )  { 
			// First get from earlySingletonObjects according to beanName 
			// Get, this is a map, key is beanName, value is semi-finished 
			// bean instance, as a secondary cache to resolve circular dependencies 
			singletonObject =  this . earlySingletonObjects . get ( beanName ) ; 
			// If it is not in earlySingletonObjects, and it is allowed to 
			get bean instance through // ObjectFacotry's getObject method 
			if  ( singletonObject == null && allowEarlyReference )  { 
				// Get from singletonFactories, this is a map 
				// key is beanName, value is ObjectFactory, through its getObject method, you can // get a specific bean instance, the map is used as a 
				// third-level cache to resolve circular dependencies
				
				ObjectFactory < ? > singletonFactory =  this . singletonFactories . get ( beanName ) ; 
				// if singletonFactory is not null 
				if  ( singletonFactory != null )  { 
					// <2021-03-26 11:39> 
					// call its getObject method to get bean 
					singletonObject = singletonFactory . getObject ( ) ; 
					// Add the bean obtained from singleFactories#getObject to // 
					this . earlySingletonObjects in the second level cache
					 earlySingletonObjects .put ( beanName , singletonObject ) ; 
					// At this point, the semi-finished bean has been stored in the second-level cache map earlySingletonObjects 
					//, the third-level cache singletonFactories is useless 
					// so delete 
					this from it . singletonFactories . remove ( beanName ) ; 
				} 
			} 
		} 
	} 
	// return a singleton bean 
	return singletonObject ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

Because it is more important, let's take a look at the execution flow chart of the program before starting the analysis:
insert image description here
the role of the three-level cache is as follows:

cache levelcache variable nameeffect
L1 cachesingletonObjectsStore the final singleton bean
L2 cacheearlySingletonObjectsExpose singleton beans ahead of time
L3 cachesingletonFactoriesStores factories that create singleton beans, and handles situations such as the need for proxies

<2021-03-25 16:19>reference3.2.1: Get a singleton bean from the first-level cache,<2021-03-25 16:20>The point is to judge whether there is a circular dependency. If it is not obtained from the singleton cache, and the current beanName is still being created, it means that there is a circular dependency, and it is judged that the method is being created.isSingletonCurrentlyInCreationas follows:

org . springframework . beans . factory . support . DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation
 public  boolean  isSingletonCurrentlyInCreation ( String beanName )  { 
	// singletonsCurrentlyInCreation is a collection that stores the name of the singleton bean being created 
	// if it is included then 
	return  this . singletonsCurrentlyInCreation .contains ( beanName ) ; } _

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

<2021-03-26 11:39>The point is to obtain the ObjectFactory corresponding to the singleton bean from the third-level cache singletonFactories. For details, refer to3.2.2: Get beans from the L3 cache.

3.2.1: Get a singleton bean from the first-level cache

singletonObjectsdefined asprivate final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);where the key isbean name, value isthe final available bean instance, this is also spring's solution to circular dependenciesL1 cache in L3 cache, so here is simply to get a bean from a map through the bean name, then when is the bean put in, in the methodorg.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton, let's take a look at the following initialization process:
insert image description here
the red background is the code to execute here, you can follow this process to debug the test, the source code is as follows:

org . springframework . beans . factory . support . DefaultSingletonBeanRegistry#addSingleton
 protected  void  addSingleton ( String beanName , Object singletonObject )  { 
	synchronized  ( this . singletonObjects )  { 
		// Add to L1 cache 
		this . singletonObjects . put ( beanName , singletonObject ) ; 
		/ / Deleted from the third-level cache, in fact, it should have been deleted at this time, but to be on the safe side, after executing 
		this .singletonFactories . remove ( beanName ) ; // remove 
		this from the second level cache 
		. earlySingletonObjects . remove ( beanName ) ; // add to the set of registered bean names this . registeredSingletons . add ( beanName ) ; } }
		
		
	

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.2.2: Get beans from the L3 cache

variablesingleFactoriesdefined asprivate final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);, which is the third-level cache used by spring to resolve circular dependencies. ObjectFactory is a functional interface defined as follows:

@FunctionalInterface 
public  interface  ObjectFactory < T >  { 
	T getObject ( )  throws BeansException ; 
}
  • 1
  • 2
  • 3
  • 4

only one waygetObjectIt is used to return the internally created bean, so the function of this interface is to create a bean. Since it can be obtained from here, there must be a place to put the information, specifically inorg.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBeanThe core method name of the call made here is roughlygetBean->doGetBean->createBean->doCreateBean, the source code is as follows:

org . springframework . beans . factory . support . AbstractAutowireCapableBeanFactory#doCreateBean
 protected Object doCreateBean ( final String beanName ,  final RootBeanDefinition mbd ,  final  @Nullable Object [ ] args ) 
			throws BeanCreationException { 
	. . . snip . . . 
	// if the following conditions are met allow early exposure of beans 
	// 1: is a singleton 2: allow circular references 3: the current bean is being created 
	booleanearlySingletonExposure =  ( mbd . isSingleton ( )  &&  this . allowCircularReferences && 
			isSingletonCurrentlyInCreation ( beanName ) ) ; 
	// if early exposure is allowed 
	if  ( earlySingletonExposure )  { 
		if  ( logger . isTraceEnabled ( ) )  { 
			logger . trace ( "Eagerly caching bean ' "  + beanName + 
					"' to allow for resolving potential circular references") ; 
		} 
		// <2021-03-26 15:29> 
		// Add to L3 cache singletonFactories 
		addSingletonFactory ( beanName ,  ( )  - >  getEarlyBeanReference ( beanName , mbd , bean ) ) ; 
	} 
	. . . snip . . . 
	return exposedObject ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

addSingletonFactoryIt is to add ObjectFactory to the third-level cache singletonFactories, the source code is as follows:

org . springframework . beans . factory . support . DefaultSingletonBeanRegistry#addSingletonFactory
 protected void  addSingletonFactory  ( String beanName , ObjectFactory < ? > singletonFactory )  { 
	Assert . notNull ( singletonFactory ,  "Singleton factory must not be null" ) ; 
	Level 1 cache singletonObjects lock 
	synchronized  ( this . singletonObjects )  {
		// If it is not already in the first level cache singletonObjects 
		if  ( ! this . singletonObjects . containsKey ( beanName ) )  { 
			// add to the third level cache singletonFactories 
			this . singletonFactories . put ( beanName , singletonFactory ) ; 
			// from the second level cache remove 
			this from earlySingletonObjects . earlySingletonObjects . remove ( beanName ) ; 
			// add to the set of registered singleton beans 
			this . registeredSingletons . add (beanName ) ; 
		} 
	} 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

insingletongFacotrymethod is through() -> getEarlyBeanReference(beanName, mbd, bean)defined, then when calling itsgetObjectmethod through methodgetEarlyBeanReferenceTo generate a singleton bean that needs to be exposed in advance, let's take a look at this method:

org . springframework . beans . factory . support . AbstractAutowireCapableBeanFactory#getEarlyBeanReference
 // parameter beanName: standard bean name 
// parameter mdb: original beandefinition of the bean to be generated 
// parameter bean: early semi-finished bean, it can be considered as just calling the constructor The bean has not been filled with properties and initialization. 
// The dynamic weaving of AOP is done here (important!!!) 
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 ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Tags: Spring's handling of circular dependencies in singleton mode

spring java spring

Related: Spring's handling of circular dependencies in singleton mode