Spring circular reference source in-depth analysis version

@

Catalog

Preface

There are a lot of analysis articles about Spring's circular quotation on the Internet. The level of writing varies. Although I have read them and know what's going on, I still forget them after a period of time. I haven't forgotten the main skills, but I just need to remember the main points

But if you want to know more about it, you still need to see the source code analysis wave. Because when others analyze, you can't get some knowledge points. Only when you go into the source code to see, you can get more! For example, many articles on the Internet analyze how Springs solves circular dependency, but why can only be a single class, Prototype, and where can not, or why can't constructor injection. Finally, if we solve circular dependency, or how to change Chinese writing method to solve the problem.

On the paper, I have to know that I will do it! This sentence is dedicated to you who are reading the article. After reading it, remember to like it. Also, download the Spring source code to have a look

text

OK, enter the text, of course, it's not bullshit. I think readers should know if they don't know about Spring's circular reference. Let's count on a code!

@Component
public class CycleTestServiceA {  
  private CycleTestServiceB b;
  public void setB(CycleTestServiceB b) {
    this.b = b;
  }
}

@Component
public class CycleTestServiceB {  
  private CycleTestServiceA a;
  public void setA(CycleTestServiceA a) {
    this.a = a;
  }
}

The above code is a common set injection method. A depends on B in a, and B depends on a in B, which leads to circular dependency. Component is Singleton by default

Analysis

We start from the creation of spring Bean as the entry point. In the Spring IoC container, a complete Bean needs to go through the instantiation and initialization stage

The process of Spring Bean instantiating getBean

Let's go to the source code to see the process of getBean

doGetBean

There are many implementation classes for the BeanFactory interface methods when using getBean methods. We followed in his abstract implementation class, org/springframework/beans/factory/support/AbstractBeanFactory.java, which actually called the doGetBean method

Here is the core code I intercepted

   protected <T> T doGetBean(
   		final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
   		throws BeansException {

   	final String beanName = transformedBeanName(name);
   	Object bean;

      	/*
       * Key entry to handle circular dependency when detecting whether there are cached objects
       * Remember this code. I'll be back
      	* */
   	Object sharedInstance = getSingleton(beanName);
   	if (sharedInstance != null && args == null) {
   		if (logger.isDebugEnabled()) {
   			if (isSingletonCurrentlyInCreation(beanName)) {
   				logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
   						"' that is not fully initialized yet - a consequence of a circular reference");
   			}
   			else {
   				logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
   			}
   		}
   		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   	}
   	else {
        	/*
   		*Prototype bean Is there any place in the creation that indicates the circular dependency is generated to handle the Bean circular dependency
   		*This is why when Scope is Prototype, it will report the error of circular dependency. Take a look and explain later
   		* */
   		if (isPrototypeCurrentlyInCreation(beanName)) {
   			throw new BeanCurrentlyInCreationException(beanName);
   		}

   	    ...

   		if (!typeCheckOnly) {
   			markBeanAsCreated(beanName);//This method is to add the current bean to the set set set of alreadyCreated, and then some judgment needs to be made
   		}

   		try {
   		    ...
   			
   			/*
   			* The dependency on obtaining the dependency of Bean is that the depends-on dependency that we can sometimes configure in xml is not the same as the circular dependency we are talking about this time
   			* Let me make a special note
   			* */
   			String[] dependsOn = mbd.getDependsOn();
   			if (dependsOn != null) {
   				for (String dep : dependsOn) {
   				  //Register dependency to create Bean, etc
   				}
   			}

   			/*
   			* If you create a createBean in a single column, remember this code and I'll come back
   			* */
   			if (mbd.isSingleton()) {
   				sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
   					@Override
   					public Object getObject() throws BeansException {
   						try {
   							return createBean(beanName, mbd, args);
   						}
   						catch (BeansException ex) {
   						  ...
   						}
   					}
   				});
   				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
   			}

   			/*
   			* Prototype object  
   			* */
   			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);
   			}
   			/*
   			* It's not a Singleton or Prototype. It may be an object of a custom scope
   			* */
   			else {
   			   ...
   			}
   		}
   	}
       ...
   	return (T) bean;
   }

Above is the core method of dogetBean()

Why Prototype can't

With this problem, we can see from the above code that Spring has two methods before Prototype creation(), after Prototype creation(),
Up and down code

/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
			new NamedThreadLocal<Object>("Prototype beans currently in creation");

    protected void beforePrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
		}
		else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<String>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
		}
		else {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
	}
	
	protected void afterPrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal instanceof String) {
			this.prototypesCurrentlyInCreation.remove();
		}
		else if (curVal instanceof Set) {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.remove(beanName);
			if (beanNameSet.isEmpty()) {
				this.prototypesCurrentlyInCreation.remove();
			}
		}
	}

I believe that the above code can be understood by all my friends. It is to use a set set set to store the BeanName of the Bean currently being created, and to use ThreadLocal to store the ThreadLocal of the set set set is private for each thread. Now let's look at the code and see the judgment of isprototype currentlycreation

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

See? This is to use the set set in ThreadLocal to judge. Why do you think of ThreadLocal? A depends on B, while B depends on a and AB are Prototype. When a is created, a will be added to the set set. Then when a fills in the instance, because it depends on B, it will get B. when B depends on a, it will get a, You can see that when the top judgment isprototype currentlycreation is executed, the circular reference error is not reported, because a is already in the set set set of prototypes currentlycreation, because the whole process must be a thread going down, so it is stored in ThreadLocal, no problem at all, and it is not affected by other threads~

createBean

No matter what kind of Scope is to call the createBean method, we follow the code and find that the only overriding implementation is in org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
Let's take a look at the code

    protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
		RootBeanDefinition mbdToUse = mbd;
	    ...
		try {
			// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
			// This function gives the BeanPostProcessors a chance to return a proxy object to the postprocessor
			// This is an important place to realize AOP processing
			// AOP is implemented through the BeanPostProcessor mechanism, and the interface, instantiaionawarebeanpostprocessor, is the focus of implementing the proxy
			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
			if (bean != null) {
				return bean;
			}
		}
		...

		/*
		* The postprocessor creates it without returning a valid bean
		* */
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
		if (logger.isDebugEnabled()) {
			logger.debug("Finished creating instance of bean '" + beanName + "'");
		}
		return beanInstance;
	}

I see an English comment here, but I'm not willing to replace Chinese. Give BeanPostProcessors a chance to return a proxy instead of the target bean instance In order to achieve the role of agent, we will talk about AOP slowly later! This core code is still in doCreateBean, continue to follow up

doCreateBean

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

        // Instantiate the bean.
		BeanWrapper instanceWrapper = null;//BeanWrapper is Bean's wrapper class, which facilitates the operation of Bean instances
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
		Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
		mbd.resolvedTargetType = beanType;

		....
		
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));//Whether the single column running circular reference bean is being created
		if (earlySingletonExposure) {
			addSingletonFactory(beanName, new ObjectFactory<Object>() {
				@Override
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);//Expose references ahead of time to get early references
				}
			});
		}

		// Initialize the bean instance
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);//Fill Bean
			if (exposedObject != null) {
				exposedObject = initializeBean(beanName, exposedObject, mbd);//Execute the method in the initialization Bean
			}
		}
		...

		if (earlySingletonExposure) {
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			   /*
			   *In fact, we have made a judgment here. exposedObject has passed the initializeBean method 
			   *The bean is still the one exposed in advance,
			   *Why do you want to make this judgment? It's because the exposedObject has been modified by the post processor in the initializeBean. Maybe the Object has changed 
			   **/
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				}
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}
					/*
					*If you are interested in each of the above methods, this is to judge that if the beans exposed in advance have been modified and are different from those in the postprocessor, an exception will be thrown, because the beans exposed in advance may be used as a dependency of other beans, which will lead to the appearance of two instances of a single class Bean in the container, which is illegal!
					*/
					if (!actualDependentBeans.isEmpty()) {
					  //Throw an exception and I'll delete a lot of words
					}
				}
			}
		}
	   ...
		return exposedObject;
	}

The main concern of earlySingletonExposure is the code on the earlySingletonExposure side. This is the code sent between properties after the Bean instantiation is completed
There are three conditions for earlySingletonExposure to be true

  • Single class
  • Allow circular references
  • Single class is being created at that time

The first two can understand what's the last one? Let's look at the entry method if we don't talk much

private final Set<String> singletonsCurrentlyInCreation =
			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}

This method is very simple to judge whether the current Bean is in the set collection of singletons currentlycreation. When was this collection added? With this idea in mind, I re scanned a piece of code that I still remember. In the org/springframework/beans/factory/support/AbstractBeanFactory.java code, the doGetBean() method in Singleton's Bean calls the getSingleton method when creating an Instance. If you don't know, you can look up

getEarlyBeanReference

This method is the addSingletonFactory method, which returns the usage method when building the parameters of ObjectFactory
Look at the code:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
					if (exposedObject == null) {
						return null;
					}
				}
			}
		}
		return exposedObject;
	}

The most important thing is to look at the smartinstantiaionawarebaeanpostprocessor method in getEarlyBeanReference method. There are two methods in its implementation: org / springframework / beans / factory / config / instantiaionawarebaeanpostprocessoradapter.java and dynamic agent. I won't list them,

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

In fact, the rewriting of the instantiaionawarebeanpostprocessoradapter returns the current bean without any operation. In fact, a reference is saved here.

getSingleton

The code is located in org/springframework/beans/factory/support/DefaultListableBeanFactory.java

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		synchronized (this.singletonObjects) {
			Object singletonObject = this.singletonObjects.get(beanName);
			if (singletonObject == null) {
			    ...
				beforeSingletonCreation(beanName);
				boolean newSingleton = false;
			    ...
				try {
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}
				finally {
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return (singletonObject != NULL_OBJECT ? singletonObject : null);
		}
	}

This method is the core creation process of singleton bean

beforeSingletonCreation

protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

When I see this singletons currentlycreation.add, I'm very pleased because my previous problem has been solved, which is to put Bean into the previous singletons currentlycreation collection

singletonFactory.getObject

It should be clear that the anonymous ObjectFactory object passed in by our method will execute the createBean method we just looked at when we get the object

afterSingletonCreation

protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

It's also very simple to see what's in the aftersingleton creation method, which is removed from the singletons currentlycreation collection

addSingleton

Look at the code first

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

You can't see the following three levels of cache in the table

  • singletonObjects instantiation and initialization completed bean cache
  • earlySingletonObjects can refer to the bean cache in advance, in which the bean is an incomplete bean, and the properties are filled with the beans that have not been executed by the postprocessor
  • The creation factory function object of singletonFactories

Speaking of this, we know that this method is to delete the current bean in the second level cache and the third level cache, and put the current bean into the first level cache, because at this stage, the bean instantiation, property filling, post processor execution, initialization and other methods have been executed.

addSingletonFactory

Where is this method used? We need to go back to the above code, doCreateBean. When earlySingletonExposure is true, this method, addsingletofactory, will be called
This method is the method that the current Bean can execute if it is referenced in advance
Look at the code. It's very simple

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

Look at this method to make it clear that the place where the ObjectFactory object of the Bean is stored in the third level cache is also the key to handling circular references. At this time, the Bean just instantiated some column methods, such as property filling and initialization, which have not yet been implemented

How to solve the problem of early quotation? You can see that the ObjectFactory returns the getEarlyBeanReference object

getSingleton(beanName)

This method is to get the Bean object from the cache in the doGetBean method. The key point of this method is to deal with the entry of circular dependency. Let's follow it

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return (singletonObject != NULL_OBJECT ? singletonObject : null);
	}

The final call method is as follows: allowEarlyReference is true. Let's use the top ServiceA and ServiceB as examples. When ServiceA enters for the first time, it is impossible to enter. The following judgment should be made: the current ServiceA is not in singletoncurrentlycreation, but when is the second time to enter, when is the second time to fill ServiceB You need to rely on ServiceB. At this time, ServiceB also needs to execute the process of getBean, and find that it depends on ServiceA, At this time, ServiceA is in the collection of singletoncurrentlycreation and in the level-3 cache. At this time, it will determine the method in the condition, first find the level-1 cache, then find the level-2 cache, finally find the level-3 cache, and then take out the ObjectFactory in the level-3 cache to execute the getObject method It is to get the bean s that we mentioned earlier, put them into the second level cache, and remove them from the third level cache~

Core notes

After reading the above push, maybe it's silly, maybe it's also my poor ability of text organization, which can only be changed slowly in the future

Description of the cache

There are several caches mentioned above. I'm rewriting them on the edge

Name type instructions Genus
singletonObjects Map<String, Object> Instantiate the cache of bean s whose initialization is complete DefaultSingletonBeanRegistry.java
earlySingletonObjects Map<String, Object> The bean cache that can be referenced in advance, in which the bean is an incomplete bean, and the properties are filled with the beans that have not been executed by the postprocessor DefaultSingletonBeanRegistry.java
singletonFactories Map<String, ObjectFactory<?>> Creation factory function object of single class bean DefaultSingletonBeanRegistry.java
singletonsCurrentlyInCreation Set Collection of singleton beans to be created DefaultSingletonBeanRegistry.java
prototypesCurrentlyInCreation ThreadLocal object is a Set of prototype beans to be created AbstractBeanFactory.java
alreadyCreated Set bean s that have been created at least once will be used for judgment when the beans exposed in advance are modified to cause inconsistency AbstractBeanFactory.java

Execution flow chart

Finally, I use a flow chart of method execution to describe the processing of next cycle dependency


Injection solution of constructors

So why is the injection mode of the constructor not working? The reason is that when bean creates an instance in the instantiation phase, it will create A dependent B. in this way, A will not go to the code block exposed in advance, so it will report A circular reference error. The error is where the constructor parameter bean is created. You can write A demo yourself, The blogger took A long time to find out which step error was reported under debugging, ha ha!

resolvent

On how to solve the cyclic injection of constructors
https://www.baeldung.com/circular-dependencies-in-spring
This is a foreign blog. You can read it

  • Use lazy loading
  • Modify how to use setter injection
  • Using PostConstruct annotations
  • How to initialize bean postprocessor

summary

The core of Spring's handling of circular dependency is the three-level cache, which enables beans to be exposed in advance, referenced in advance, and executed on the process by interdependent beans, thus solving the problem of circular dependency

At the end of the day, I'll understand the source code by myself. I'm sure that it will deepen your understanding and gain

It's not easy to code. It took a weekend. If you like, please give me a compliment and encourage bloggers to continue creating. Thank you very much~

Tags: Java Spring xml

Posted on Sun, 10 May 2020 08:40:33 -0700 by Rolando_Garro