Loading Bean s for Spring Ioc Source Analysis: Cyclic Dependency Processing

In the previous article, we analyzed step 2 of doCreateBean() in detail: instantiating bean s, and then we analyzed step 4 of doCreateBean() called "cyclic dependency processing", which is populateBean() method.

First, review the main process of Bean loading:

  1. In the case of singleton mode, get the BeanWrapper instance object from the factoryBeanInstanceCache cache and delete the cache
  2. Call createBeanInstance() to instantiate the bean
  3. Post processing
  4. Cyclic Dependency Processing for Singleton Patterns
  5. Attribute fill
  6. Initialize bean instance objects
  7. Dependency checking
  8. Register Disposable Bean

In this chapter, we mainly analyze step 4:

First, what is cyclical dependence?

Circular dependency, in fact, is circular reference, which means that two or more bean s refer to each other and eventually form a closed loop, such as A dependent B, B dependent C, C dependent A. As shown in the following figure:

The cyclic dependency in Spring is actually a dead-cycle process. When the dependency B is found in the initialization of A, it will initialize B. Then it will find that B depends on C and runs to initialize C. When the dependency A is found in the initialization of C, it will initialize A, and the cyclic will never exit unless there is an end condition.

Generally speaking, there are two cases of Spring cyclic dependency:

Constructor cyclic dependency. Circular dependencies of field attributes. Spring can't solve the cyclic dependencies of constructors. It can only throw BeanCurrentlyInCreationException exceptions to represent cyclic dependencies, so the following analysis is based on field attribute cyclic dependencies.

In the foreword Spring Ioc Source Analysis Bean Loading (3): Bean Creation for Each scope As mentioned in the article, Spring only solves the cyclic dependency of scope as singleton. For a bean whose scope is prototype, Spring can't solve it by throwing a BeanCurrentlyInCreationException exception directly.

Why doesn't Spring handle prototype bean s? In fact, if you understand how Spring solves the cyclic dependencies of singleton beans, you will understand. Let's leave a question here. Let's first look at how Spring solves the cyclic dependencies of singleton beans.

2. Solving singleton Cyclic Dependence

In the doGetBean() method of AbstractBeanFactory, when we retrieve Singleton Bean s based on BeanName, we first retrieve them from the cache.
The code is as follows:

//DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Loading bean s from the first-level cache singleton Objects
    Object singletonObject = this.singletonObjects.get(beanName);
    // The bean in the cache is empty and the current bean is being created
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // Lock up
        synchronized (this.singletonObjects) {
            // Get from the secondary cache earlySingleton Objects
            singletonObject = this.earlySingletonObjects.get(beanName);
            // EarlySingleton Objects are not available and allow early creation
            if (singletonObject == null && allowEarlyReference) {
                // Obtain the corresponding ObjectFactory from the three-level cache singleton Factories
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //Getting bean s from a singleton factory
                    singletonObject = singletonFactory.getObject();
                    // Add to Level 2 Cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // Delete from tertiary cache
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

The three key variables involved in this code are three levels of caching, defined as follows:

	/** Cache of singleton objects: bean name --> bean instance */
	//Cache Level 1 Cache for Singleton Beans
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name --> ObjectFactory */
	//Singleton Object Factory Cache Level 3 Cache
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name --> bean instance */
	//Preloaded single bean cache secondary cache
	//Stored bean s are not necessarily complete
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

The logic of getSingleton() is clear:

  • First, try to retrieve the singleton beans from the first-level cache singleton Objects.

  • If not, the singleton Bean is retrieved from the secondary cache earlySingleton Objects.

  • If it is still not available, the singleton Factory is retrieved from the three-level cache singleton Factories.

  • Finally, if the BeanFactory is taken from the third-level cache, the Bean is stored in the second-level cache through getObject(), and the third-level cache of the Bean is deleted.

2.1, Level 3 cache

Looking at this, there may be some questions. How can these three caches solve the singleton cycle dependency?
Don't worry, now we've analyzed the code to get the cache, and then we'll look at the code to store the cache. In the doCreateBean() method of AbstractAutowireCapableBeanFactory, there is such a piece of code:

// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() // Singleton mode
        && this.allowCircularReferences // Allow cyclic dependencies
        && isSingletonCurrentlyInCreation(beanName)); // Is the current singleton bean being created
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // In order to avoid cyclic dependencies later, the created bean instance is added to the three-level cache singleton Factories in advance.
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

This code is where the put three-level cache singleton Factories is located. Its core logic is to add bean s to the three-level cache when the following three conditions are met:

  • Single case
  • Allow cyclic dependencies
  • The current singleton Bean is being created

The addSingletonFactory (String bean Name, ObjectFactory<?> singletonFactory) method is coded as follows:

// DefaultSingletonBeanRegistry.java

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);
		}
	}
}

From this code, we can see that singleton Factories, a three-tier cache, is the key to resolving Spring Bean cyclic dependencies. At the same time, this code occurs after the createBeanInstance(...) method, which means that the bean has actually been created, but it is not yet perfect (no attribute filling and initialization), but it is enough for other objects that depend on it (there are memory addresses, which can be located in the heap according to object references) to be recognized. Now.

2.2, Level 1 Cache

Here we find that the values in the third-level cache singleton Factories and the second-level cache earlySingleton Objects have their origins. Where is the first-level cache set? In the DefaultSingletonBeanRegistry class, you can find this addSingleton(String beanName, Object singletonObject) method, which is coded as follows:

// DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
	        //Add to the first level cache and delete from the second and third level cache.
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

This method is in the # doGetBean(...) method, when dealing with different scope s, if it is called by singleton, as shown in the following figure:

That is to say, the first level cache contains a complete Bean.

Summary:

  • Inside the first level cache is a complete Bean, which is put after a Bean is fully created.
  • Level 3 caching is an incomplete BeanFactory, where a Bean is put after new (no attribute filling, initialization)
  • Level 2 caching is the ease of use of Level 3 caching, but only by getObject() method to extract beans from the Bean Factory of Level 3 caching.
summary

Now let's review Spring's solution to single-case cyclic dependency:

  • Spring does not wait for the beans to be created completely, but exposes the ObjectFactory of the beans created in advance during the creation process (i.e., adding it to the singleton Factories three-tier cache).

  • In this way, once the next bean needs to rely on the bean when it is created, it is retrieved from the three-tier cache.

Take a chestnut:
For example, if you want to sign up for an activity in our team, you don't need to come up and fill in all your birthday, gender and family information. You just need to put in a name first, count the number of people, and then gradually improve your personal information.

Core idea: expose in advance, use first

Finally, describe the process of relying on Spring to solve the above cycle:

  • First, A completes the first step of initialization and exposes itself in advance (exposing itself in advance through a three-level cache). When initializing, it finds itself dependent on object B, and then it tries to get(B). At this time, it finds that B has not been created yet.

  • Then B goes to the creation process, and when B is initialized, it also finds itself dependent on C, and C is not created.

  • At this time, C begins to initialize the process, but finds itself dependent on A in the initialization process, so it tries to get(A). At this time, because A has been added to the cache (three-level cache singleton Factories) and exposed in advance through ObjectFactory, it can get A object through ObjectFactory#getObject() method. After C gets A object, it completes the initial process smoothly. Change, and then add yourself to the first level cache

  • Back to B, B can also get the C object, complete the initialization, A can smoothly get B to complete the initialization. Here the whole link is initialized.

Finally, why can't multiple-case patterns solve cyclic dependencies?
Because each new() Bean is not one in the multi-instance mode, if stored in the cache in this way, it becomes a singleton.

Reference resources:
http://cmsblogs.com/?p=todo (Xiao Ming Ge)

Keywords: Programming Spring Attribute Java

Added by peyups on Mon, 14 Oct 2019 05:56:26 +0300