1. What are circular dependencies

Cyclic dependency means that B is injected into A, and A is injected into B. When A is initialized, B needs to be initialized first, and then A needs to be initialized again, resulting in A deadlock phenomenon.

2. How to solve Spring

2.1. Examples of cyclic dependencies

@Component
public class A {
	@Autowired
	public B b;
}

@Service("b")
public class B {
	@Autowired
	public A a;
}
Copy the code

In springIOC initialization process, we learned that the lazy loading of singleton beans are AbstractApplicationContext. Refresh () method call finishBeanFactoryInitialization () method, Finally through AbstractBeanFactory. DoGetBean initialized () method. How Spring resolves loop dependencies must also be under this method [Spring 5.2 source code].

2.2. Spring_bean generates sequence diagrams

Before reading the source code, it is helpful to look at the sequence diagram generated by the bean.

  1. Spring tries to passApplicationContext.getBean()Method to get an instance of the A object, since the Spring container does not yet have an A object [getSingleton(A) = null], so Spring creates an A object. Spring creates A bean in three steps: 1. Instantiate the bean, 2. And store the semi-finished product of object A [uninjected attributes, uninitialized] in the three-level cache [addSingletonFactory(A)].
  2. Then inject property B into object A, passgetBean(B)Try to get B objects from the Spring container. Since the Spring container does not have B objects yet, B objects will be created.
    • Create a semi-finished object of B and store it in a level 3 cache [addSingletonFactory(B)].
    • We then inject property A into the B object, and use getBean(A) to get A reference to A’s semi-finished object from the level 3 cache, moving A from level 3 to Level 2 cache. And inject it as a property into the B object
    • After the B object is initialized, the B object is returned. Save the B object to the level 1 cache
  3. Finally, the returned B object is injected into A object as A property, initialized A, and A object is saved in the level 1 cache.

2.3 Interpretation of source code

The lazy loading of singleton Spring_Bean eventually through AbstractBeanFactory. DoGetBean initialized (). Our source code analysis also starts with this approach.

Ignore any code that is not relevant for this time

protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	String beanName = transformedBeanName(name);
	Object bean;
	// Check whether the target bean is already registered.
	Object sharedInstance = getSingleton(beanName);
	if(sharedInstance ! =null && args == null) {}else {
		/ / the singleton
		if (mbd.isSingleton()) {
			// Add the obtained bean to the level 1 cache
			sharedInstance = getSingleton(beanName, () -> {
				try {
					// Create a bean object
					return createBean(beanName, mbd, args);
				}catch(BeansException ex) { } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }}return (T) bean;
}
Copy the code

There are three noteworthy approaches:

  1. getSingleton(beanName);
  2. getSingleton(beanName,ObjectFactory);
  3. createBean(beanName, mbd, args);

Let’s take a closer look at the source code for these three methods.

2.3.1, getSingleton (beans)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Query level-1 cache
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// Query the level 2 cache
		singletonObject = this.earlySingletonObjects.get(beanName);
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						// Query the level-3 cacheObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
						// Write the queried data to level 2 cache and remove it from level 3 cache
						if(singletonFactory ! =null) {
							singletonObject = singletonFactory.getObject();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}
Copy the code
  1. This method attempts to retrieve the bean from the level 1, level 2, and level 3 caches, returning the object directly if it can get it, or null otherwise.
  2. isSingletonCurrentlyInCreationThe method is used to determine whether the target bean is in the creation phase, and returns true if so.
  3. If the referenceallowEarlyReferenceIf true, an attempt will be made to query the level 3 cache. The bean object is stored in the level 2 cache after being queried in the level 3 cache.

2.3.2、createBean(beanName, mbd, args)

The second argument in the getSingleton(beanName,ObjectFactory) method is provided by createBean(beanName, MBD, args), so let’s examine the createBean method first.

CreateBean(beanName, MBD, args) delegates to the same doCreateBean(beanName, mbdToUse, args) method implementation.

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
		throws BeanCreationException {
	// BeanWrapper is the bean wrapper class that provides the getPropertyDescriptors method to get the bean's properties
	BeanWrapper instanceWrapper = null;
	if (mbd.isSingleton()) {
		instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
	}
	// Create a wrapper class for the bean instance
	if (instanceWrapper == null) {
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	// earlySingletonExposure. 2. Beans allow circular dependencies. 3. The singleton bean is being created
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
			isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isTraceEnabled()) {
			logger.trace("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		The getEarlyBeanReference method returns the bean object in the input parameter by default
		// At this point, the bean has no injected properties, i.e. annotations such as @autowried have not been resolved
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	Object exposedObject = bean;
	try {
		// Inject attributes, @autowired, etc are parsed here
		populateBean(beanName, mbd, instanceWrapper);
		// The bean is initialized
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	}
	catch (Throwable ex) {}
	// Remove some relatively unimportant code
    return exposedObject;
}
Copy the code

The three steps of spring_bean generation:

  1. CreateBeanInstance (beanName, MBD, args)
  2. Property injection: populateBean(beanName, MBD, instanceWrapper);
  3. Initialize: initializeBean(beanName, exposedObject, MBD);

There is a key line of code between instantiation and property injection: addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, MBD, Bean));

GetEarlyBeanReference (beanName, MBD, beans) method will delegate AbstractAutoProxyCreator. WrapIfNecessary dynamic proxy () returns a bean proxy classes of objects.

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if(specificInterceptors ! = DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);
	Object proxy = createProxy(
	bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
	this.proxyTypes.put(cacheKey, proxy.getClass());
	return proxy;
}
Copy the code

The proxy class is then added to the level 3 cache with addSingletonFactory().

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.registeredSingletons.add(beanName); }}}Copy the code

populateBean(beanName, mbd, instanceWrapper)

This method resolves the properties in the bean class that need to be injected. If it is a bean object that needs to be injected, Spring gets the corresponding bean by reflection and eventually calls the getBean() method again.

Because of this case is injected through the @autowired annotation attribute, the @autowired is injected through the rear AutowiredAnnotationBeanPostProcessor processor attribute.

Let’s look at how properties are injected, okay

// Get the @autowired modified attribute that needs to be injected
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
	// Get the bean_name to be injected from the metadata, and then finally get it from the getBean() method
	metadata.inject(bean, beanName, pvs);
}
Copy the code

Final call descriptor. ResolveCandidate (autowiredBeanName, type, this); Gets the bean to inject

public Object resolveCandidate(String beanName, Class
        requiredType, BeanFactory beanFactory)
		throws BeansException {
	return beanFactory.getBean(beanName);
}
Copy the code

This call link is a bit long, so I won’t repeat it here. Those who are interested can view them by themselves according to the following link

  • AbstractAutowireCapableBeanFactory.populateBean()
  • AutowiredAnnotationBeanPostProcessor.postProcessProperties()
  • AutowiredAnnotationBeanPostProcessor.inject()
  • DefaultListableBeanFactory.resolveDependency()
  • DefaultListableBeanFactory.doResolveDependency()
  • DependencyDescriptor.resolveCandidate()

conclusion

After reviewing the above source code, we will review the interview question “How does Spring solve loop dependency?”

The Spring container has three caches (singletonObjects, earlySingletonObjects, and singletonFactories). After the bean is instantiated, Spring adds references to the bean object to the three caches. When a loop dependency occurs, Spring first injects a reference to an incomplete bean that is being initialized as a property. Finally, the bean object is initialized and added to the level 1 cache [singletonObjects].

Assuming that A and B have cyclic dependencies, register A with the Spring container first.

  1. First try to fetch the bean object in Spring’s level 3 cache. If not, create A new bean A
  2. A is instantiated, and references to incomplete OBJECTS of A are saved to A level 3 cache.
  3. Then inject property B into A and find that the object B is not in the Spring container
    • Instantiate B, and then store incomplete references to B objects in a level 3 cache.
    • Then, attribute A is injected into B, and object A is found to exist in level 3 cache through query, and object A is saved in level 2 cache. And then it goes into B. Insert A reference to A into B
    • After B is initialized, object B is now a complete bean and stored in a level 1 cache.
  4. After B is successfully created, a reference to the B object is returned. And inject it into A.
  5. After A is initialized, A is also A complete bean, so the A property reference object in B is also complete, and is saved to the level 1 cache.

Although circular dependencies exist, they still report errors in the case of constructor injection.

Setter injection and annotation injection are recursive (getBean) on the populateBean method. At this point, the third level cache has been saved, so the circular dependency does not go wrong.

But the constructor injected recursion occurs in the createBeanInstance(beanName, MBD, args) method. At this point, the level 3 cache has not been saved, and if it fails to get the bean from the cache, the new bean will be created again. This leads to circular dependencies.

Interested partners can follow the source code.

Code link:

  • createBeanInstance(beanName, mbd, args)
  • AbstractAutowireCapableBeanFactory.autowireConstructor()
  • ConstructorResolver.autowireConstructor()
  • ConstructorResolver.resolveConstructorArguments()
  • BeanDefinitionValueResolver.resolveValueIfNecessary();
  • BeanDefinitionValueResolver.resolveReference()
    • Finally, the getBean() method is called in this method