preface

Speaking of the solution to Spring’s cyclic dependency, I believe that many users are more or less aware of some, but when it comes to explaining it in detail, it may not be able to explain it clearly. This paper tries to do his best to make a more detailed interpretation of this. Instantiation refers to the state in which an object has just been created by executing the constructor but has not yet filled in the property values, while initialization refers to the completion of the dependency injection of the property.

1. What are circular dependencies

Here’s an example of what a cyclic dependency is:

There are two classes, A and B, with the following code:

/** * loop dependency A * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class A {public B getB() {return B; } public void setB(B b) { this.b = b; } private B b; } /** * loop dependency B * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class B {public A getA() {return A; } public void setA(A a) { this.a = a; } private A a; }Copy the code

There is attribute B in A, and attribute A in B, which depends on the following figure:

What problems do circular dependencies cause?

When learning the bean life cycle, the bean has to go through the initialization process after instantiation, and the initialization needs to assign values to the bean properties. However, when the instance of A is initialized, B has not been instantiated, so the attribute assignment of A fails.

2. Spring Solutions

Students, the content of related must know spring solution for the circular dependencies is to use level 3 cache, there is an important kind of DefaultSingletonBeanRegistry sping source code, in this defines three levels of cache, as shown below:

/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); */ Private final Map<String, ObjectFactory<? >> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);Copy the code

The essence of this is that bean instantiation and initialization are handled separately, with caching enabling early exposure of semi-finished (uninitialized) products.

Spring has set and constructor injection methods. Do these two methods solve the problem of loop dependency?

The main method:

import org.springframework.context.support.ClassPathXmlApplicationContext; /** * main * @date: Public class TestSpring {public static void main(String[] args) {public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); }}Copy the code

The set method:

/** * loop dependency A * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class A {public B getB() {return B; } public void setB(B b) { this.b = b; } private B b; } /** * loop dependency B * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class B {public A getA() {return A; } public void setA(A a) { this.a = a; } private A a; }Copy the code

The XML file:

        <bean id="a" class="com.demo.A">
            <property name="b" ref="b"></property>
        </bean>
        <bean id="b" class="com.demo.B">
            <property name="a" ref="a"></property>
        </bean>
Copy the code

The result of execution, which can be solved by the test, is cyclic dependent:

Constructor mode:

/** * loop dependency A * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class A {private B B; public A(B b) { this.b = b; }} /** * loop dependent B * @date: 2020/12/24 * @author weirx * @version 3.0 */ public class B {private A A; public B(A a) { this.a = a; }}Copy the code

The XML configuration:

<bean id="a" class="com.demo.A">
        <constructor-arg ref="b"></constructor-arg>
    </bean>
    <bean id="b" class="com.demo.B">
        <constructor-arg ref="a"></constructor-arg>
    </bean>
Copy the code

If the fault cannot be resolved, you will get the following error message:

十二月 25, 2020 9:38:00 上午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'b' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'b' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:704)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:196)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1356)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1203)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
    at com.demo.TestSpring.main(TestSpring.java:14)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [spring-beans.xml]: Cannot resolve reference to bean 'a' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:704)
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:196)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1356)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1203)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
    ... 17 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
    ... 29 more
Disconnected from the target VM, address: '127.0.0.1:60287', transport: 'socket'

Process finished with exit code 1
Copy the code

3. Source tracking

Follow the flow chart of the source code process

Here’s a flow chart of my source tracking process:

The key point in the figure above is that in Spring source code, it is common to see getBean, doGetBean, etc. In the process of solving the loop dependency, the important process is shown in the figure below:

Code tracing begins:

Classes A and B adopt set mode

Execute the main method:

/** * main * @date: Public class TestSpring {public static void main(String[] args) {public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml"); }}Copy the code

Tracking the refresh method finishBeanFactoryInitialization method, tracking the last line of code inside directly the beanFactory. PreInstantiateSingletons () :

public void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { logger.trace("Pre-instantiating singletons in " + this); } List<String> beanNames = new ArrayList<>(this.beandefinitionNames); String beanName: String beanName: String beanName: BeanNames) {/ / get the definition of a bean RootBeanDefinition bd = getMergedLocalBeanDefinition (beanName); // Bean is not abstract & singleton & lazy loading if (! bd.isAbstract() && bd.isSingleton() && ! Bd.islazyinit ()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName); if (bean instanceof FactoryBean) { FactoryBean<? > factory = (FactoryBean<? >) bean; boolean isEagerInit; if (System.getSecurityManager() ! = null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged( (PrivilegedAction<Boolean>) ((SmartFactoryBean<? >) factory)::isEagerInit, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<? >) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); }}} else {// getBean getBean(beanName); } } } // Trigger post-initialization callback for all applicable beans... for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; if (System.getSecurityManager() ! = null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { smartSingleton.afterSingletonsInstantiated();  return null; }, getAccessControlContext()); } else { smartSingleton.afterSingletonsInstantiated(); }}}}Copy the code

GetBean method

The code ignores unimportant flow and the breakpoint goes straight to the getBean method:

Public Object getBean(String name) throws BeansException {return doGetBean(name, NULL, null, false); }Copy the code

Trace into the doGetBean, two key points, check the registered cache (getSingleton(beanName)), and create the bean instance, all annotated in the following code to focus on:

protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); 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); } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory ! = null && ! containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args ! = null) { // Delegation to parent with explicit args. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType ! = null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (! typeCheckOnly) { markBeanAsCreated(beanName); } try { RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. String[] dependsOn = mbd.getDependsOn(); if (dependsOn ! = null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); }}} // Create bean instance if (mbd.issingleton ()) {// Get bean instance from cache, using function interface (ObjectFactory is a function interface, when calling its default method, SharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, MBD, args); } 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); throw ex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); if (! Stringutils.haslength (scopeName)) {throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); }}); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. if (requiredType ! = null && ! requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } catch (TypeMismatchException ex) { if (logger.isTraceEnabled()) { logger.trace("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }Copy the code

Let’s look at each of these two key points and see what they do. GetSingleton = null; getSingleton = null; getSingleton = null;

@Nullable protected Object getSingleton(String beanName, Boolean allowEarlyReference) {/ / see if there are any current level 1 cache the bean instance of the Object singletonObject = this. SingletonObjects. Get (beanName); // Check whether the current object in the cache is null. And is currently being created beans if (singletonObject = = null && isSingletonCurrentlyInCreation (beanName)) {synchronized (this singletonObjects) {/ / from the second level cache the bean instance singletonObject = this. EarlySingletonObjects. Get (beanName); If (singletonObject == null && allowEarlyReference) {// Get the bean instance ObjectFactory<? > singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory ! = null) {/ / perform in functional interface methods singletonObject = singletonFactory. GetObject (); / / add bean to the second level cache enclosing earlySingletonObjects. Put (beanName singletonObject); / / remove the l3 cache this. SingletonFactories. Remove (beanName); } } } } return singletonObject; }Copy the code

If the instance does not exist in the query cache, it needs to be created. Part of the code block to create the instance is as follows. Here we focus on the getSingletion method and here is a lambda expression:

// Create bean instance if (mbd.issingleton ()) {// Get bean instance from cache, using function interface (ObjectFactory is function interface, when calling its default method, SharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, MBD, args); } 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); throw ex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }Copy the code

Let’s go to the getSingleton method. The second argument, ObjectFactory<? >, this object is a functional interface. When the default method inside the executor, getObject, calls createBean, the content of the previous getSingleton lambda expression; The following method after obtaining level cache, found did not take to the bean instance, after some judgment performed behind singletonFactory. GetObject (), enter the createBean phase:

public Object getSingleton(String beanName, ObjectFactory<? > singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); Synchronized (enclosing singletonObjects) {/ / get beans from level 1 cache Object singletonObject = this. SingletonObjects. Get (beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!) "); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } the try {/ / call the ObjectFactory default method, perform createBean singletonObject = singletonFactory. GetObject (); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; }}Copy the code

Enter the createBean method, where the main method is doCreateBean:

@Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { if (logger.isTraceEnabled()) { logger.trace("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Make sure bean class is actually resolved at this point, and // clone the bean definition in case of a dynamically resolved Class // which cannot be stored in the shared merged bean definition. Class<? > resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass ! = null && ! mbd.hasBeanClass() && mbd.getBeanClassName() ! = null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides. try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } the try {/ / let BeanPostProcessors have the opportunity to return to an agent instead of the target bean instance 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); } Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // A previously detected exception with proper bean creation context already, // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry. throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); }}Copy the code

Xiaobian share content is over here!

Xiaobian here sorted out some Java core technology knowledge collection of more than 200 pages of information collection; Spring Boot learning notes complete tutorial 100 pages of information documentation, attention to the public number: Kirin bug change. The world of programming is always open to all people who love programming. It is a world of freedom, equality and sharing. I always believe that.