How Spring solves bean circular dependency from source code

Keywords: Java xml Spring Attribute encoding

1 what is bean circular dependency

circular reference is the original text of circular dependency, which refers to the mutual reference of multiple objects to form a closed loop.

Take the circular dependency of two objects as an example:

There are three cases of circular dependency in Spring:

  1. The cyclic dependence of the constructor;
  2. Circular dependency of field;
  3. Constructor and field circular dependency.

Spring can solve case 2 and case 3, but spring can't solve case 1. When a constructor circular dependency occurs, an exception is thrown:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cat' defined in class path resource [constructor.xml]: Cannot resolve reference to bean 'dog' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dog': Requested bean is currently in creation: Is there an unresolvable circular reference?

or

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  securityConfig defined in file [E:\IDEA\lab-back-end\target\classes\com\lpc\config\SecurityConfig.class]
↑     ↓
|  userServiceImpl defined in file [E:\IDEA\lab-back-end\target\classes\com\lpc\service\impl\UserServiceImpl.class]
└─────┘

2 preparation

Prepare two POJO s

public class Dog {
    private String name;
    private Cat friend;

    public Dog() {
    }

    public Dog(String name, Cat friend) {
        this.name = name;
        this.friend = friend;
    }

    // Omit getter and setter
}
public class Cat {
	private String name;
	private Dog friend;

	public Cat() {
	}

	public Cat(String name, Dog friend) {
		this.name = name;
		this.friend = friend;
	}

	// Omit getter and setter
}

Cat class has a member variable whose type is Dog class, and Dog class has a member variable whose type is cat class, which satisfies the condition of circular dependency.

Source code analysis of 3-field cyclic dependency

3.1 field.xml

Prepare a field.xml, which configures two bean s:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="dog" class="Dog">
		<property name="name" value="dog"/>
		<property name="friend" ref="cat"/>
	</bean>

	<bean id="cat" class="Cat">
		<property name="name" value="cat"/>
		<property name="friend" ref="dog"/>
	</bean>
</beans>

Two beans are declared in xml, and another bean is referenced to its own member variable through attribute injection. Load xml in the main class:

ClassPathXmlApplicationContext ac
      = new ClassPathXmlApplicationContext("field.xml");

3.2 method call chain

Method call process summary can jump to 3.12 directly.

3.3 AbstractBeanFactory#doGetBean()

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // Omit some codes
	Object bean;
	// major measure
	// Check whether there is manual registration of singleton bean in singleton cache
	// It actually calls defaultsingletonbeanregistry × getsingleton (beanname, true)
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		// Omit some codes
	}
	else {
		// Omit some codes
        try {
			// Omit some codes
            if (mbd.isSingleton()) {
                // major measure
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        // major measure
                        // The specific implementation of singletonFactory.getObject(). AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
                        return createBean(beanName, mbd, args);
                    }
                    // Omit some codes
                });
                // Omit some codes
            }
            // Omit some codes
    }
    // Omit some codes
    return (T) bean;
}

Here are two pieces of code to focus on.

  • Object sharedInstance = getSingleton(beanName);

    This method actually calls getSingleton(beanName, true);. See 3.4 for details.

  • sharedInstance = getSingleton(beanName, () -> {
        try {
            // major measure
            // The specific implementation of singletonFactory.getObject(). AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
            return createBean(beanName, mbd, args);
        }
        // Omit some codes
    });
    

    See 3.5 for details.

3.4 DefaultSingletonBeanRegistry#getSingleton(String, boolean)

The DefaultSingletonBeanRegistry class has three Map type member variables, which are also called three-level cache.

// L1 cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// L2 cache
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// L3 cache
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

// In addition to the three-level cache, there is a Set in which the bean name is saved to indicate that the bean is being created
private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<>(16));
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Three level cache is actually three maps, and the key is bean name
	// 1. Get from singleton objects
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// If it is not obtained from the first level cache, and the object is being created
        // The way to determine whether the object is being created is to determine whether the beanName is in the singletoncurrentlycreation Set
		synchronized (this.singletonObjects) {
			// 2 get from the second level cache earlySingletonObjects
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				// If it is not obtained in the secondary cache, and allowEarlyReference is true
				// 3.1 get from the three-level cache. At this time, get the ObjectFactory. This is a factory, not an instance
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// 3.2 getting an instance from an object factory
					singletonObject = singletonFactory.getObject();
					// 3.3 save the acquired instance to the second level cache (why not save to the first level cache)
					this.earlySingletonObjects.put(beanName, singletonObject);
					// 3.4 remove factory from L3 cache
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

3.5 DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>)

3.5 is actually the heavy load of 3.4.

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		// Omit some codes
		synchronized (this.singletonObjects) {
		// Get bean s from L1 cache
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null) {
			// Omit some codes

			// major measure
			// This method will add the beanName to the singletons currentlycreation Set
			// This step will work when getsingleton (string, Boolean) is called later
			beforeSingletonCreation(beanName);
			
			// Omit some codes

			try {
				// major measure
				// In this step, the object is created
				// singletonFactory is an anonymous inner class passed in as a parameter
				singletonObject = singletonFactory.getObject();
				// Omit some codes
			} 
			// Omit some codes
		}
		return singletonObject;
	}
}

Here are two pieces of code to focus on.

  • beforeSingletonCreation(beanName);

    This method adds the beanName to the singletons currentlycreation Set. See 3.6 for the method. For the definition of singletons currently in creation, see 3.4.

  • singletonObject = singletonFactory.getObject();

    singletonFactory is the parameter of the method. In fact, it is the anonymous inner class created by lambda in the second important code of 3.3. In fact, it calls the doCreateBean(String, RootBeanDefinition, jObject []) of AbstractAutowireCapableBeanFactory class

3.6 DefaultSingletonBeanRegistry#beforeSingletonCreation()java

protected void beforeSingletonCreation(String beanName) {
   // In creationcheckeexclusions singletons currentlyin creation are all sets
   // In this case, the add() method of Set is called at the same time of judgment
   // So bean name is added to singletons currently in creation
   if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
}

3.7 AbstractAutowireCapableBeanFactory#doCreateBean()

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

	BeanWrapper instanceWrapper = null;
	// Omit some codes
	if (instanceWrapper == null) {
		// major measure
		// Here, the appropriate constructor is called to generate the instance, and the instance is placed in a wrapper class. It's not going to be detailed here
		instanceWrapper = createBeanInstance(beanName, mbd, args);
	}
	// Take the instance out of the wrapper class
	final Object bean = instanceWrapper.getWrappedInstance();
	// Omit some codes

	// The result of this boolean value depends on - > singleton, allowCircularReferences is true (the default is true), and the object is being created
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		// Omit some codes
		// major measure
		// This method will add the bean to the L3 cache
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}


	// Initialize the bean instance.
	Object exposedObject = bean;
	try {
		// major measure
		// Assign a value to a property
		populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	} 

	// Omit some codes
}

Here are three important pieces of code:

  • instanceWrapper = createBeanInstance(beanName, mbd, args);

    This method selects the appropriate constructor to build the instance.

  • addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

    In this method, beanName and beanFactory are put into the three-level cache. See 3.8 for details.

  • populateBean(beanName, mbd, instanceWrapper);

3.8 AbstractAutowireCapableBeanFactory#createBeanInstance()

3.9 DefaultSingletonBeanRegistry#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)) {
         // If the beanName does not exist in the first level cache
         // Save in Level 3 cache
         this.singletonFactories.put(beanName, singletonFactory);
         // Remove in L2 cache
         this.earlySingletonObjects.remove(beanName);
         // Add in registered singletons
         this.registeredSingletons.add(beanName);
      }
   }
}

In this method, beanName and beanFactory are put into the three-level cache. At this time, the three-level cache already has the dog object I want to create (in fact, there is a factory to create this object).

3.10 AbstractAutowireCapableBeanFactory#populateBean()

This method calls the AbstractAutowireCapableBeanFactory class applyPropertyValues() method, and the applyPropertyValues() method calls the BeanDefinitionValueResolver class resolveReference() method. Finally, we call doGetBean(), which is the method of 3.3.

doGetBean() is here to create its Cat class member variable for the dog object.

3.11 supplement

In the defaultsingletonbeanregistry ා getsingleton (string, objectfactory <? >) method, addSingleton(beanName, singletonObject);; is also called, which will add the bean to the first level cache and remove it from the second and third level cache. Of course, this step has nothing to do with resolving circular dependency.

3.12 summary

Summarize the whole process:

  1. According to the order of the bean s declared in field.xml, the creation of the dog object is carried out first;
  2. Go to the third level cache to get the dog object, which is not available at this time, so null is returned;
  3. Add beanName (also known as "dog") to the singletons currently creating Set;
  4. Through the judgment conditions of some columns, the appropriate constructor (here is the no parameter construction) is selected to build the dog instance. At this time, the member variables of the dog object are null;
  5. Put the beanName (that is, "dog") into the third level cache;
  6. Assign a value to the property of the dog object. At this time, it is found that the member variables of the dog object need to be injected into a cat class object. At this time, create the cat object. The creation process is equivalent to steps 2 to 6;
  7. The same as step 2, go to level 3 cache to get cat object, which is not available at this time, so null is returned;
  8. In the same step 3, the beanName (also known as "cat") is added to the singletons currently creating Set;
  9. As in step 4, select the appropriate constructor (here is the no parameter construction) to build the cat instance. At this time, the member variables of the cat object are all null;
  10. In step 5, put the bean name (that is, "cat") into the third level cache;
  11. As in step 6, assign a value to the attribute of the cat object. At this time, it is found that the member variable of cat object needs to inject an object of dog class. Execute step 2, and find that the third level cache can already get the dog object. Then take out the dog object, save it to the second level cache, and remove the dog object from the third level cache. Now you can assign the dog object to the member variable of the cat object. Cat object creation completed;
  12. Back to step 6, assign the cat object to the member variable of the dog object, and the dog object is also created.

In general, Spring solves the problem of circular dependency by relying on the third level cache, and recursively creates the beans on which the current bean depends.

This paper is a platform of operation tools such as blog group sending one article and multiple sending OpenWrite Release

Posted by scotthoff on Sun, 26 Apr 2020 19:48:31 -0700