Circular dependencies

Circular dependencies are pretty straightforward, where two classes refer to each other before, for example

public class Aservice {

	Bservice bservice;

	public Bservice getBservice(a) {
		return bservice;
	}

	public void setBservice(Bservice bservice) {
		this.bservice = bservice; }}public class Bservice {

	Aservice aservice;

	public Aservice getAservice(a) {
		return aservice;
	}

	public void setAservice(Aservice aservice) {
		this.aservice = aservice; }}Copy the code

How does Spring IoC solve this problem if, when resolving dependencies, it does not handle this problem and then falls into a dependency infinite loop, injecting Bservice when creating Aservice and injecting Aservice when creating Bservice? In summarizing the principles of IoC, it was briefly mentioned at juejin.cn/post/697692…

In the first place in the org. Springframework. Context. Support. Add a unit test class ClassPathXmlApplicationContextTests

@Test
public void mytest(a) {
	ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( PATH + "test/myContext.xml");
	assertTrue(ctx.containsBean("aservice"));
}
Copy the code

Add myContext.xml

<bean name="aservice" class="org.springframework.context.Aservice">
	<property name="bservice" ref="bservice"/>
</bean>

<bean name="bservice" class="org.springframework.context.Bservice">
	<property name="aservice" ref="aservice"/>
</bean>
Copy the code

Start the mytest.

The overall train of thought

Spring IoC mainly USES three Map as a cache, the cache beans of different state, in the org. Springframework. Beans. Factory. Support. DefaultSingletonBeanRegistry

SingletonObjects: Holds instantiated beans

SingletonFactories: Stores ObjectFactory

EarlySingletonObjects: Stores beans that have not yet been instantiated, which are pre-exposed beans

Where the loop dependency core is addressed

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null&& allowEarlyReference) { ObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
				if(singletonFactory ! =null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

In the org. Springframework. Beans. Factory. Support. AbstractBeanFactory# doGetBean in two key areas:


// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName); / / 1.
if(sharedInstance ! =null && args == null) {
	if (logger.isDebugEnabled()) {
		if (isSingletonCurrentlyInCreation(beanName)) {
			logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
					"' that is not fully initialized yet - a consequence of a circular reference");
		}
		else {
			logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
		}
	}
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); }...// Create bean instance.
if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, () -> { 
		try {
			return createBean(beanName, mbd, args); / / 2.
		}
		catch (BeansException ex) {
			// Explicitly remove instance from singleton cache: It might have been put there
			// eagerly by the creation process, to allow for circular reference resolution.
			// Also remove any beans that received a temporary reference to the bean.
			destroySingleton(beanName);
			throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }Copy the code

At the beginning, all three maps are empty

SQL > select aservice from singletonObjects; SQL > select createBean from singletonObjects; CreateBean goes all the way to createBean -> doCreateBean -> addSingletonFactory. In addSingletonFactory, singletonObjects does not have aserVice. I’m just going to add aservice. That’s ③ down here

There are also three key points in the doCreateBean method

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isDebugEnabled()) {
		logger.debug("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); / / 3.
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
	populateBean(beanName, mbd, instanceWrapper); / / 4.exposedObject = initializeBean(beanName, exposedObject, mbd); }...if (earlySingletonExposure) {
	Object earlySingletonReference = getSingleton(beanName, false); / / 5.
	if(earlySingletonReference ! =null) {
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			String[] dependentBeans = getDependentBeans(beanName);
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			if(! actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName,
						"Bean with name '" + beanName + "' has been injected into other beans [" +
						StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
						"] in its raw version as part of a circular reference, but has eventually been " +
						"wrapped. This means that said other beans do not use the final version of the " +
						"bean. This is often the result of over-eager type matching - consider using " +
						"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); }}}}Copy the code

When you get to doCreateBean (④), Aservice will call getBean again because it depends on Bservice. The Bservice is also in singletonFactories. When you get to doCreateBean (④), Bservice relies on the Aservice, so it calls getBean again. However, when it enters the “earlySingletonObjects” factories, it adds the “Aservice” to the “earlySingletonObjects”. Exposure in advance

However, the Aservice is still an uninstantiated Bean, so getBean is returned here

3. Back to populateBean, the Aservice that BService depends on has been injected. In the following initializeBean, bService has been instantiated

Pay attention to the status of the three maps:

SingletonObjects: null

SingletonFactories: bservice

EarlySingletonObjects: aservice

Complete bservice has already been instantiated. As a result, the code need to go back to (2) the getSingleton method, continue singletonObject = singletonFactory… getObject (); Go down behind the

And then the code will go to the last addSingleton(beanName, singletonObject)

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

When the code goes here, the state of the three maps

SingletonObjects: bservice

SingletonFactories: null

EarlySingletonObjects: aservice

4. After the bService has been handled, the code needs to go back to the place where populateBean was executed when the Aservice was created. Aservice is still in earlySingletonObjects, but it is important to note that the BService of the Aservice class has been injected at this point (pause here and think about why).

Since the aservice is in earlySingletonObjects, the code does not go inside ⑤, but returns to ② to continue, which is the last addSingleton(beanName, singletonObject), as in 3

When the code enters the addSingleton again, it adds the Aservice to singletonObjects and spills over from earlySingletonObjects

At this point, the status of the three maps

SingletonObjects: BService, aservice

SingletonFactories: null

EarlySingletonObjects: null

At this point, we have solved the problem of circular dependencies

Conclusion:

This sort of work makes me think it’s, in a word, messy. It was a bit difficult to sort out the whole idea, so my solution was as follows: (1) Understand the function of 3 maps

(2) In the getBean, find the key places to get and put the three maps

(3) Sort out the main line of their own understanding

This is a draft of my own work

(4) Debug on drafts and key points, and then correct your own understanding

(5) At the same time output blog, tidy up their own ideas