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