In the previous article, we looked at step 2 in doCreateBean(), instantiating the bean, in detail, and this article goes on to look at step 4 in doCreateBean(), “cyclic dependency handling,” which is the populateBean() method.

Let’s review the main flow of CreateBean:

  1. If it is singleton mode, get the BeanWrapper instance object from the factoryBeanInstanceCache cache and remove the cache
  2. callcreateBeanInstance()Instantiate the bean
  3. Post processing
  4. Singleton pattern for loop dependency processing
  5. fill
  6. Initialize the bean instance object
  7. Depend on the check
  8. Register the bean’s destruction method

In this chapter, we mainly analyze step 4:

What are cyclic dependencies?

Circular dependency, in fact, is circular reference, that is, two or more beans reference each other, and eventually form A closed loop, such as A dependent on B, B dependent on C, C dependent on A. As shown below:

Circular dependencies in the Spring, but in fact is the process of an infinite loop, when initializing A find rely on B, will go to initialize B at this moment, and then found that rely on C, B went to initialize C, initialization of time found that rely on A, C, and will go to initialize A, in turn, cycle never quit, unless there is an end.

In general, there are two cases of Spring loop dependencies:

Constructor loop dependencies. Loop dependencies for the field property. For circular dependencies constructor, Spring is unable to solve, can only throw BeanCurrentlyInCreationException abnormal said circular dependencies, so here we are based on the analysis of the field properties of circular dependencies.

As mentioned in the previous Spring Ioc source code analysis of Bean loading (3) : Bean creation of each scope, Spring only resolves the loop dependency of scope singleton. For the scope of the prototype bean, Spring can’t solve, direct selling BeanCurrentlyInCreationException anomalies.

Why doesn’t Spring handle the Prototype bean? If you understand how Spring addresses the circular dependencies of the Singleton bean, you’ll see. To leave the question open, let’s take a look at how Spring resolves the circular dependencies of the Singleton bean.

2. Resolve singleton loop dependencies

AbstractBeanFactory in AbstractBeanFactory’s doGetBean() method, we fetch the Singleton Bean by BeanName from the cache first. The code is as follows:

//DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Load beans from the level 1 cache singletonObjects
    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
        synchronized (this.singletonObjects) {
            // From the secondary cache earlySingletonObjects
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects does not exist and allows pre-creation
            if (singletonObject == null && allowEarlyReference) {
                // Get the corresponding ObjectFactory from singletonFactoriesObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
                if(singletonFactory ! =null) {
                    // Get beans from the singleton factory
                    singletonObject = singletonFactory.getObject();
                    // Add to level 2 cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // Delete from level 3 cache
                    this.singletonFactories.remove(beanName); }}}}return singletonObject;
}
Copy the code

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

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

	/** Cache of singleton factories: bean name --> ObjectFactory */
	// Singleton factory cache level 3 cache
	private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);

	/** Cache of early singleton objects: bean name --> bean instance */
	// Preload the singleton bean cache level 2 cache
	// The stored bean may not be complete
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
Copy the code

The logic of getSingleton() is clear:

  • First, try to get the singleton Bean from the level-1 cache singletonObjects.

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

  • If you still can’t get it, get the singleton BeanFactory from the level-three cache singletonFactories.

  • Finally, if you get the BeanFactory from the level 3 cache, the Bean is put into the level 2 cache with getObject() and the level 3 cache of the Bean is deleted.

2.1. Three-level cache

How can these three caches solve the singleton loop dependency? Now that we’ve analyzed the code that gets the cache, let’s look at the code that stores the cache. In AbstractAutowireCapableBeanFactory doCreateBean () method, so a piece of code:

// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() // Singleton mode
        && this.allowCircularReferences // Allow cyclic dependencies
        && isSingletonCurrentlyInCreation(beanName)); // Whether the current singleton bean is being created
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // Add the created bean instances to the singletonFactories level 3 cache in advance to avoid loop dependencies later
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Copy the code

This is where we put the singletonFactories (level-3 cache). The core logic is to add beans to the level-3 cache if three conditions are met:

  • The singleton
  • Allow cyclic dependencies
  • The singleton Bean is currently being created

addSingletonFactory(String beanName, ObjectFactory
singletonFactory) method 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); }}}Copy the code

As you can see from this code, the singletonFactories three-level cache is the key to solving Spring Bean loop dependencies. This code also happens at createBeanInstance(…) Method, that is, the bean is actually created, but it is not yet complete (no property population or initialization), but is sufficient for other objects that depend on it (memory addresses that can be located to objects in the heap based on object references) to be recognized.

2.2 level 1 Cache

The “SingletonObjects” and the “earlySingletonObjects” caches are all referenced. To be found in the class DefaultSingletonBeanRegistry this addSingleton (String beanName, Object singletonObject) method, the code is as follows:

// DefaultSingletonBeanRegistry.java

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

This method is done in #doGetBean(…) Method, if it is called by Singleton when processing different scopes, as shown in the figure below:

Summary:

  • Level 1 caches contain complete beans, which are put only after a Bean is fully created
  • Level 3 caching is an incomplete BeanFactory, which is put when a Bean is new (no property populated, initialized).
  • Level 2 caching is an ease-of-use treatment of level 3 caching, just throughgetObject()Method to retrieve beans from the BeanFactory of the tertiary cache

conclusion

Now let’s review Spring’s solution to singleton dependency:

  • Instead of waiting for the bean to be fully created, Spring exposes the ObjectFactory of the bean being created (that is, adds it to the singletonFactories three-level cache) ahead of time.

  • This way, when the next bean is created and needs to depend on the bean, it is fetched from the level 3 cache.

For example, if you want to sign up for an activity in our team, you don’t need to fill out your birthday, gender, family information, etc., you just need to give your name first, count the number of people, and then slowly improve your personal information.

Core idea: expose in advance, use first

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

  • First, A completes the first initialization step and exposes itself in advance (through the three-level cache to expose itself in advance). During initialization, A finds that it is dependent on object B and tries to get(B), which has not been created yet

  • B then goes through the creation process, and when B initializes, it also finds that it depends on C, and C is not created

  • C tries to get(A) because A has been added to the cache (singletonFactories). So ObjectFactory#getObject() is used to get the A object, and C gets the A object and initializes it, and then adds itself to the level 1 cache

  • So if you go back to B, B can get C and initialize it, and A can get B and initialize it. At this point, the entire link has been initialized

Finally, why doesn’t the multi-example pattern solve circular dependencies? Because there is not one new() Bean at a time in the multi-instance mode, if you store it in the cache this way, it becomes a singleton.

Reference: cmsblogs.com/?p=todo (Xiao Mingge)