Next, let's fully analyze the circular reference of Spring today
What is circular reference? I don't need to force it. Go directly to the code and debug and analyze the source code in the whole process
@Configuration @ComponentScan({"com.carry"}) public class Appconfig { }
@Component public class X { @Autowired private Y y; public X() { System.out.println("x Construction method of"); } }
@Component public class Y { @Autowired private X x; public Y() { System.out.println("y Construction method of"); } }
Let's first remember these two concepts - spring bean (hereinafter referred to as bean) and object;
1. Spring beans - objects managed by the spring container may have gone through the full spring bean life cycle (why is it possible? Are there any beans that have not gone through the bean life cycle? The answer is yes, we will analyze the details later), and finally exist in the spring container; A bean must be an object
2. Object -- any object instantiated according to java syntax rules, but an object is not necessarily a spring bean
OK, let's get to the point. Let's start with doGetBean and look at the doGetBean method
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. //First, get the singleton bean from the cache. For the singleton bean, the entire IOC container is created only once Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // Get the instance object of a given bean, mainly to complete the relevant processing of factorybean bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { //There are no singleton bean s in the cache //If we have created this bean instance, //However, the instantiation of the object failed due to the problem of circular reference if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. //Check whether there is a BeanDefinition with the specified name in the ioc container. Check it first // The required Bean can be obtained from the current BeanFactory. If it cannot delegate the parent class of the current container // Find it. If it is not found yet, find the parent container along the inheritance system of the container (normally, this parentBeanFactory must be null) BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { // Not found -> check parent. String nameToLookup = originalBeanName(name); if (parentBeanFactory instanceof AbstractBeanFactory) { return ((AbstractBeanFactory) parentBeanFactory).doGetBean( nameToLookup, requiredType, args, typeCheckOnly); } else if (args != null) { // Delegation to parent with explicit args. //The delegate parent container looks up based on the specified name and explicit parameters return (T) parentBeanFactory.getBean(nameToLookup, args); } else { // No args -> delegate to standard getBean method. //The delegate parent container looks up based on the specified name and type return parentBeanFactory.getBean(nameToLookup, requiredType); } } // Whether the created Bean needs type verification is generally not required if (!typeCheckOnly) { // The Bean specified by the container tag has been created markBeanAsCreated(beanName); } try { // It mainly solves the problem of merging public attributes of subclasses and parent classes during Bean inheritance RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of beans that the current bean depends on. // Gets all beans that the current Bean depends on String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } //Recursively call the getBean method to obtain the dependent bean of the current bean registerDependentBean(dep, beanName); try { //Register the dependent Bean with the currently dependent Bean getBean(dep); } } } // Create bean instance. if (mbd.isSingleton()) { // Here, an anonymous inner class is used to create a bean instance object and register it with the dependent object sharedInstance = getSingleton(beanName, () -> { try { // Create an instance object of the specified bean. If there is parent inheritance, merge the definitions of the child class and the parent class return createBean(beanName, mbd, args); } }); // Gets the instance object of the given Bean bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // If it is a prototype pattern bean object else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { // Callback beforePrototypeCreation method. The default function is to register the currently created prototype object beforePrototypeCreation(beanName); // Create bean prototypeInstance = createBean(beanName, mbd, args); } finally { //Callback the afterPrototypeCreation method. The default function tells the IOC container that the prototype object of the specified Bean is no longer created afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } //If the Bean to be created is neither a singleton pattern nor a prototype pattern, it is defined in the resource according to the Bean //Select the appropriate method to instantiate the Bean, which is used in Web applications //More commonly used, such as request, session, application and other life cycles else { String scopeName = mbd.getScope(); if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } } } } // Check if required type matches the type of the actual bean instance. // Type check the created bean instance object if (requiredType != null && !requiredType.isInstance(bean)) { try { T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType); if (convertedBean == null) { throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } return convertedBean; } } return (T) bean; }
This doGetBean is too long. Let's simplify it:
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); // Define an object to store the Bean to be returned Object bean; // Eagerly check singleton cache for manually registered singletons. //First get the singleton bean from the cache. For the singleton bean, the whole IOC container is created only once //doGetBean1 Object sharedInstance = getSingleton(beanName); //doGetBean2 if (sharedInstance != null && args == null) { // Get the instance object of a given bean, mainly to complete the relevant processing of factorybean bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { //There are no singleton bean s in the cache //If we have created this bean instance, //However, the instantiation of the object failed due to the problem of circular reference //doGetBean3 if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Create bean instance. //doGetBean4 if (mbd.isSingleton()) { // Here, an anonymous inner class is used to create a bean instance object and register it with the dependent object sharedInstance = getSingleton(beanName, () -> { try { // Create an instance object of the specified bean. If there is parent inheritance, merge the definitions of the child class and the parent class return createBean(beanName, mbd, args); } }); } } return (T) bean; }
Next, we will analyze the marked doGetBean
doGetBean1:getSingleton
spring checks whether the beanName has been manually registered in the singleton pool before creating a bean. This is the first time
Initialization, must be null
To sum up, Object sharedInstance = getSingleton(beanName); At present, it is mainly used to judge whether the bean is in the container when spring initializes the bean; And for programmers to get a bean directly.
Note that the author uses the word "present" here; Because getSingleton(beanName); This method has a lot of code; The logic in it is the most important code to realize circular dependency. I will go back to the full meaning of this method below;
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; }
Next, we will analyze doGetBean2
//doGetBean2 if (sharedInstance != null && args == null) { // Get the instance object of a given bean, mainly to complete the relevant processing of factorybean bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); }
Since sharedInstance = =null will not enter the if branch, then what time is it not equal to null?
1. After spring initialization, the sharedInstance obtained when the programmer calls getBean("x") is not equal to null;
2. In the case of circular dependency, it is not equal to empty when the object is obtained for the second time; For example, X depends on Y; Y depends on X; When spring initializes, X must be equal to null at the first execution, and then execute down. When y is injected into the attribute, y will also execute here. Then y is also null, because y is not initialized, y will also execute down. When y is injected into the attribute, obtain X in the acquisition container, that is, obtain x at the second execution; At this time, X is not empty; As for the specific reasons, the reader will read on
As for what this method does, don't analyze it. Just post the source code and let's have a look
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { // Don't let calling code try to dereference the factory if the bean isn't a factory. //If the bean is not a factory, do not let the calling code try to dereference the factory. if (BeanFactoryUtils.isFactoryDereference(name)) { if (beanInstance instanceof NullBean) { return beanInstance; } if (!(beanInstance instanceof FactoryBean)) { throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); } } //Now we have a bean instance, which can be a normal bean or FactoryBean. //If it is a FactoryBean, we will use it to create a bean instance unless //The caller actually wants a factory reference number if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { return beanInstance; } Object object = null; if (mbd == null) { object = getCachedObjectForFactoryBean(beanName); } if (object == null) { // Return bean instance from factory. FactoryBean<?> factory = (FactoryBean<?>) beanInstance; // Caches object obtained from FactoryBean if it is a singleton. if (mbd == null && containsBeanDefinition(beanName)) { mbd = getMergedLocalBeanDefinition(beanName); } boolean synthetic = (mbd != null && mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, !synthetic); } return object; }
We then analyze doGetBean3
When spring creates bean s again, both singletons and prototypes will be add ed to this collection, and will be remove d after creation
Then analyze doGetBean4
if (mbd.isSingleton()) { // Here, an anonymous inner class is used to create a bean instance object and register it with the dependent object sharedInstance = getSingleton(beanName, () -> { try { // Create an instance object of the specified bean. If there is parent inheritance, merge the definitions of the child class and the parent class return createBean(beanName, mbd, args); } }); // Gets the instance object of the given Bean bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
Here, getSingleton is called again. If you have the impression, getSingleton is also called once. This is a method overload. The two getSingleton methods are not the same method. Readers can read the parameters themselves. In order to distinguish, this is called the second call to getSingleton; The above is called the first call to getSingleton;
Of course, the second getSingleton will create our bean. In other words, how the whole bean is initialized is in this method.
Next, we will study the content of the second getSingleton method, because I said that the whole bean initialization process is reflected in it;
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); } if (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { // Call createBean singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } }
Similarly, delete this Code:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { //getSingleton2 -1 Object singletonObject = this.singletonObjects.get(beanName); //getSingleton2 -2 if (singletonObject == null) { //getSingleton2 -3 if (this.singletonsCurrentlyInDestruction) { throw new Exception(beanName, "excepition"); } //getSingleton2 -4 beforeSingletonCreation(beanName); //getSingleton2 -5 singletonObject = singletonFactory.getObject(); } return singletonObject; }
//getSingleton2 -2 , start parsing
if (singletonObject == null) { As explained above, in spring initialization bean It must be empty when you are here, so it is established
//getSingleton2 -3 start parsing
if (this.singletonsCurrentlyInDestruction) { throw new Exception(beanName, "excepition"); }
This line of code is actually relatively simple to judge whether the currently instantiated bean is in the destroyed collection; spring is cumbersome in the process of destroying or creating a bean. It will first put them into a collection to identify whether they are being created or destroyed; So if you understand the previous one that is creating a collection, then you understand the one that is destroying; But it doesn't matter if you don't understand it. These sets will be analyzed below;
If a bean is being created but some are being destroyed, an exception will occur; Why is this happening? In fact, it is also very simple. Multithreading may be;
//getSingleton2 -4 hypothesis parsing
beforeSingletonCreation(beanName);
This code is more important. It is about the collection being created and destroyed; This code can be explained, so if you don't understand the meaning of the collection above, use the spring source code to explain it here; Let's look at the context when the code is executed here
When spring thinks it can start to create beans, it first calls beforeSingletonCreation(beanName); Judge whether the bean currently being instantiated exists in the collection being created. In other words, judge whether it is currently being created; No matter whether spring creates a prototype bean or a singleton bean, when it needs to formally create a bean, it will record that the bean is being created (add to a set set); Therefore, before formally creating the bean, he should check whether the bean is being created (whether it exists in the collection); Why should spring judge whether this collection exists? There are many reasons, except that you can think of (what you can think of basically won't appear, such as concurrency and repeated creation, because it has done strict concurrency processing). In fact, this collection is mainly for circular dependency services. How can it be served? Take your time. First, let's take a look at the details of this line of code
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && // Add beanName to this map !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
//getSingleton2 -5 start analysis
singletonObject = singletonFactory.getObject();
Some readers may have forgotten how the singletonFactory object came from; Post the code again
ObjectFactory<?> singletonFactory = new ObjectFactory(){ public Object getObject(){ //In fact, this is an abstract class and cannot be instantiated //createBean is implemented by subclasses. I don't care here //You understand that this is not an abstract class AbstractBeanFactory abf = new AbstractBeanFactory(); Object bean = abf.createBean(beanName, mbd, args); return bean; }; }; //Pass in beanName and singletonFactory objects sharedInstance = getSingleton(beanName,singletonFactory);
singletonFactory.getObject(); The GetObject method in the above code is called, in other words, ABF createBean(beanName, mbd, args); Return the created bean; So far, the second getSingleton method ends, and the bean passes through singletonfactory getObject(); Call createbean to complete the creation; Next, analyze the source code of createbean and continue to explore the principle of circular dependency;
In the AbstractAutowireCapableBeanFactory#createBean() method, the doCreateBean method is invoked to create bean.
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. //BeanWrapper is used to hold the created bean object BeanWrapper instanceWrapper = null; // If it is a singleton, clear the bean s with the same name in the cache first if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. //Call Postprocess postprocessor synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. // Cache the Bean object of singleton mode in the container to prevent circular reference boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } //This is an anonymous inner class. In order to prevent circular reference, hold the reference of the object as soon as possible addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. //Initialization of bean object, dependency injection is triggered here //After initialization, this exposedObject is returned as a Bean after dependency injection Object exposedObject = bean; try { // Encapsulate the bean instance object, and assign the attribute configured in the bean definition to the instance object populateBean(beanName, mbd, instanceWrapper); //Initialize bean object exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { // Gets the registered singleton pattern Bean object with the specified name Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { //The registered Bean obtained by name is the same as the Bean being instantiated if (exposedObject == bean) { //Initialization of the currently instantiated bean is complete exposedObject = earlySingletonReference; } //The current bean depends on other beans, and new instance objects are not allowed when circular references occur else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. //Register the bean that completes dependency injection try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
instanceWrapper = createBeanInstance(beanName, mbd, args);
As the name suggests, createBeanInstance is to create an instance. Note that this is only to create an instance object, which cannot be called a bean, but an X object is created, and the attributes have not been filled in
How to create this one depends on me Instantiation process of Spring Bean
Then analyze
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
This must be true. addSingletonFactory will be called
addSingletonFactory(beanName,singletonFactory); Add a singleton factory as the name suggests; In fact, we should pay great attention here, because when talking about spring circular dependency in most materials, it is said that a semi-finished bean is exposed in advance; I think this is not strict; It's even wrong. The so-called early exposure is add here, but we see that the source code does not add a bean, but adds a factory object - singletonfactory; What's the difference between the two statements? The difference is great, it's a world of difference; We analyze slowly; What's the difference between bean and factory? In the current context, beans are x objects that have gone through the spring life cycle; The so-called semi-finished bean may not have experienced a complete life cycle; What about factory objects? If you go to the source code of ObjectFactory, or directly as the name suggests, it is a factory that can generate objects, or a factory that can generate beans; In other words, a bean is a product, and a factory is the company that produces these products; If you can't understand it, it may be easy to understand it - the difference between ice fire and the whole set. Ice fire is an item in the whole set, and there are other items besides ice fire;
spring adds a singletonFactory object here (this factory can generate semi-finished objects), not a semi-finished object; The equivalent of adding here is a complete set, not ice and fire; In the future, it will be obtained from the factory, and then semi-finished products will be obtained through the factory; What will come out in the future is a complete set. You can choose a project arbitrarily in the complete set; I don't know. I didn't explain the problem clearly;
Of course, after all this, maybe you still don't understand why you need to add this factory object here? And where is add going?
Let's first analyze where the bean factory object add goes and check the source code
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); } } }
First level cache: there may be many beans, such as various built-in beans in spring, such as other created beans in your project, but in the process of creating X, there is absolutely NO X bean in the first level cache and it is useless y; Because spring creates beans in alphabetical order by default;
Level 2 Cache: there is only one factory object in it, the corresponding key is the beanName of X, and the GetObject method of this bean factory object can return x (x bean of semi-finished product) at this time
After the put is completed, the code is executed next;
L3 cache: let's say there's nothing in it
populateBean(beanName, mbd, instanceWrapper);
populateBean is a well-known method. It mainly completes attribute injection, which is often called automatic injection; Assuming that the code in this environment runs this line of code, y will be injected, and Y refers to x, so the injected y object also completes the injection of X; What do you mean? First, take a look at the situation before populateBean is not executed
If X is instantiated before populateBean is executed and Y is not instantiated, y cannot be injected; Next, let's look at the situation after executing this line of code
x filling y (xpy for short) must first obtain y and call getBean(y). The essence of getBean has been analyzed above. After entering the first call to getSingleton, readers can recall that in my explanation of the name of doGetBean method above, it is said that this method is used to create beans and obtain beans;
For the first time, getSingleton will get y from the singleton pool. If y does not have a singleton pool, it will start to create y
Y as like as two peas in creating x, will take the bean life cycle. For example, add y to the collection of beans being created, infer the construction method, instantiate y, expose the factory object in advance (there are now two factories in the L2 cache, X and Y respectively), etc.... Repeat the steps of X;
//TODO hasn't been in good shape recently. Please make it up later