As the most popular development framework at present, Spring is gradually familiar to everyone. This article mainly introduces the treatment scheme for cyclic dependency in Spring, mainly from the following points:
- Why do circular dependencies occur
- How does Spring address loop dependencies
- Loop dependency brings us to reflect
Part 1: Why do circular dependencies occur?
The following is just an example to help you understand a scenario where circular dependencies can occur.
The scene is introduced
To develop a settlement function, for example, the settlement process is complicated. The general settlement process is as follows: order completion > settlement detail > settlement order.
Our first impression is to design the settlement details and settlement statement into two tables in the database. Then, the settlement Details service and settlement Statement service will be corresponding to each table. When we first designed this service, the settlement details service and settlement Statement service should be settled. The settlement statement is the upper layer of the settlement details, and the Settlement Service calls the Settlement Service.
But then one day, the product manager said, Settlement details should be adjusted and the settlement result should be used in the corresponding settlement statement. If you do not care about circular dependence, SettleOrderService into a settlement service. This addresses the requirements of the product, but at this point we have circular dependencies that make our code look less elegant! However, even though we wrote some inelegant code, it didn’t seem to have any problems at startup, so many people thought there was nothing wrong with this circular reference. But is that really the case? Certainly not ah, not would not appear this article. Listen to the author.
Part 2: How does Spring solve circular dependencies?
SettleOrderService to SettleDetailService, SettleDetailService to SettleOrderService, and SettleDetailService to SettleOrderService. No error can be found when the project is started. Some curious partners will inevitably have some doubts, in the spirit of learning to the end, let’s dig into why this code does not appear to be a problem.
The nature of circular dependencies
SettleDetailServiceImpl introduces a settleOrderService, and a settleOrderServiceImpl introduces a settleDetailService. In essence, two objects refer to each other. It ends up in a closed loop.
Resolution of loop dependencies
To understand the solution to loop dependency, we need to understand the object creation process, which can be summarized in two steps: object instantiation and object property initialization. We know that object creation in Spring is brokered by the framework, so we don’t have to worry about the specifics of object creation, but it is essentially the instantiation and initialization of the object mentioned above. To solve circular dependencies, this is a concept we need to understand. To understand Spring’s approach to loop dependencies, let’s go straight to source code. (Since the Spring Bean loading process is not described in this article, I will not repeat it here, so interested readers can find out for themselves.)
The code analysis
SettleDetailServiceImpl and settleOrderServiceImpl are two objects that Spring constructs. With a look at Spring’s Bean loading process, let’s look at the AbstractBeanFactory#doGetBean() method, which looks like this:
/** * Return an instance, which may be shared or independent, of the specified bean. * @param name the name of the bean to retrieve * @param requiredType the required type of the bean to retrieve * @param args arguments to use when creating a bean instance using explicit arguments * (only applied when creating a new instance as opposed to retrieving an existing one) * @param typeCheckOnly whether the instance is obtained for a type check, * not for actual use * @return an instance of the bean * @throws BeansException if the bean could not be created */ @SuppressWarnings("unchecked") protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); **A** if (sharedInstance ! = null && args == null) { ... } else { // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // Check if bean definition exists in this factory. 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. return (T) parentBeanFactory.getBean(nameToLookup, args); } else if (requiredType ! = null) { // No args -> delegate to standard getBean method. return parentBeanFactory.getBean(nameToLookup, requiredType); } else { return (T) parentBeanFactory.getBean(nameToLookup); } } if (! typeCheckOnly) { markBeanAsCreated(beanName); } try { final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Guarantee initialization of 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 + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'",ex); } } } // Create bean instance. **B** if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }else if (mbd.isPrototype()) { ... }else { ... } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // Check if required type matches the type of the actual bean instance. ... return (T) bean; }Copy the code
AbstractBeanFactory#doGetBean()
The code is relatively long, so let’s just focus on the labeled snippet.
Code A
Check for registered singleton information from the cache.
This calls the getSingleton() method, where we might wonder why such a step of validation is required before creating an object. The answer to this question is the key to Spring’s solution to loop dependencies, so let’s continue with this question in mind.
Let’s look at the internal implementation of getSingleton(), which looks like this:
/** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ @Nullable 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; }Copy the code
DefaultSingletonBeanRegistry#getSingleton()
In the above code, we found three collection objects, singletonObjects, earlySingletonObjects, and singletonFactories. To understand the processing logic of the above code, we need to understand what these three objects represent. Check the comments in the source code to find:
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<? >> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);Copy the code
The role of the three maps
- SingletonObjects: Stores the mapping of Bean names to singleton beans
- SingletonFactories: Stores mappings between Bean names and ObjectFactory
- EarlySingletonObjects: Stores the mapping of Bean names to preloaded beans
GetSingleton () : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton() : getSingleton(); If no preloaded singleton exists, the preloaded object is fetched from earlySingletonObjects. If no preloaded singleton exists, the preloaded object is fetched from singletonFactories and the factory method is called to create the preloaded object. The pre-loaded object information created is stored in earlySingletonObjects and the factory object for the current object is removed from singletonFactories. By reading the meaning of this method, we know that the object information returned by this method is either the created singleton object information or the preloaded object information.
GetSingleton () handles the process
For those of you who are not aware of the difference between singletonObjects and earlySingletonObjects, singletonObjects stores information about objects that have already been instantiated and initialized. EarlySingletonObjects contains information about objects that have been instantiated but not initialized. (This is where the object creation process comes in.)
This is the first settlement ServiceImpl object to be retrieved from the cache. Therefore, singletonObjects, earlySingletonObjects, and singletonFactories do not have corresponding object information. The getSingleton() method returns null, as shown below:
The code segment B
If the object information cannot be obtained in code section A, the process logic of code section B is used to create an instance object. In this example, the framework tries to create A settleDetailServiceImpl object.
Let’s look at the code implementation inside the createBean() method by calling the AbstractBeanFactory#createBean() method to create the object.
/** * Actually create the specified bean. Pre-creation processing has already happened * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks. * <p>Differentiates between default bean instantiation, use of a * factory method, and autowiring a constructor. * @param beanName the name of the bean * @param mbd the merged bean definition for the bean * @param args explicit arguments to use for constructor or factory method invocation * @return a new instance of the bean * @throws BeanCreationException if the bean could not be created * @see #instantiateBean * @see #instantiateUsingFactoryMethod * @see #autowireConstructor */ protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)throws BeanCreationException { // Instantiate the bean. **C** BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<? > beanType = instanceWrapper.getWrappedClass(); if (beanType ! = NullBean.class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. ... // Eagerly cache singletons to be able to resolve circular references. **D** // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); **E** exposedObject = initializeBean(beanName, exposedObject, mbd); **F** } catch (Throwable ex) { ... } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference ! = null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } 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. ... return exposedObject; }Copy the code
AbstractAutowireCapableBeanFactory#doCreateBean
The above code is still partially deleted, we can focus on the labeled part of the code.
Code C
After the object is instantiated, a settleDetailServiceImpl object information is obtained. The attributes of the object are not initialized. The property value settleOrderService is still null.
As shown below:
Code section D
After the object has been instantiated, the code attempts to add the factory object information of the created object information to the singletonFactories Map. The factory information of the settlement ServiceImpl should be added to singletonFactories. The factory information of the settlement ServiceImpl should be added to singletonFactories. All you need to know is that the settlement ServiceImpl factory object is added to singletonFactories at this point.
As shown below:
Before factory objects are added to Map
After the factory object is added to the Map
Code segments E, F
After the object is instantiated and the factory object information is added to the singletonFactories, the settlement ServiceImpL framework starts to initialize the object. The populateBean() method is called at E. Let’s see what is done inside the poulateBean() method. Its internal code is implemented as follows:
/** * Populate the bean instance in the given BeanWrapper with the property values * from the bean definition. * @param beanName the name of the bean * @param mbd the bean definition for the bean * @param bw the BeanWrapper with bean instance */ @SuppressWarnings("deprecation") // for postProcessPropertyValues protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (bw == null) { if (mbd.hasPropertyValues()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { // Skip property population phase for null instance. return; } } // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. if (! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (! ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { return; } } } } PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); int resolvedAutowireMode = mbd.getResolvedAutowireMode(); if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Add property values based on autowire by name if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Add property values based on autowire by type if applicable. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() ! = AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; **G** PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } if (pvs ! = null) { applyPropertyValues(beanName, mbd, bw, pvs); }}Copy the code
AbstractAutowireCapableBeanFactory#populateBean
Fill the bean instance in a given BeanWrapper with property values from the bean definition. One thing this method does, we can understand, is populate the property information of the object. Let’s focus on the annotated code section G, which first gets a list of BeanPostProcessors to create the Bean, And then use the BeanPostProcessor subinterface InstantiationAwareBeanPostProcessor# postProcessProperties () method to modify the attributes of the object instance.
This simple add BeanPostProcessor and InstantiationAwareBeanPostProcessor interface; BeanPostProcessor is used to add some custom processing logic to the instantiated object after Spring has finished instantiating it. InstantiationAwareBeanPostProcessor for BeanPostProcessor interface, it besides the function of the BeanPostProcessor interface, also enhances the three methods, PostProcessBeforeInstantiation, postProcessAfterInstantiation (), postProcessProperties ().
- PostProcessBeforeInstantiation: the object is instantiated before calling this method can achieve the goal of replace the generated beans
- PostProcessAfterInstantiation: after the object is instantiated calls, the return value of this method as a determinant of whether to call postProcessPropertyValues
- PostProcessProperties: Modifies the object property value
Then the code above analysis, we found that the framework USES InstantiationAwareBeanPostProcessor# postProcessProperties () method to modify the attributes of the object instance, PostProcessProperties also has a number of concrete implementations, as shown in the following figure:
We focus on AutowiredAnnotationBeanProcessor# postProcessProperties () in the implementation, the code is as follows:
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
Copy the code
InjectionMetadata#inject
The InjectionMetadata#inject() method implements the final property injection. In our example, the SettleOrderService object is injected into the settleDetailServiceImpl object, as shown in the following figure:
The specific logic of injection is not described in detail in this article, but the trace of the specific injected logic is provided here for interested partners to study;
The specific trace is as follows:
InjectionMetadata#inject -> DefaultListableBeanFactory#resolveDependency -> DefaultListableBeanFactory#doResolveDependency -> DependencyDescriptor#resolveCandidate -> AbstractBeanFactory#getBean
When the framework attempts to inject a SettleOrderService object into the settleDetailServiceImpl, Call DefaultListableBeanFactory# doResolveDependency method to get satisfy SettleOrderService object information, because there is no meet the conditions of the framework of this time SettleOrderService instance of the object information, So the AbstractBeanFactory#getBean method is called to create an object that meets the criteria; By analyzing the injected logic above, we can draw a conclusion: when an object is created, the framework will try to create the dependent object first, when the object information does not exist. In our example, if a settleDetailServiceImpl object is to be initialized, it depends on the SettleOrderService object, but the Service does not exist yet. Create a SettleOrderService object. The settlement Service object must be the same as the settleDetailServiceImpl object.
Create a SettlOrderService object. If a SettlOrderService object is to be created for the first time, a settlement ServiceImpl object must not be created. The singletonFactories object only has information about the settleDetailServiceImpl and its factory object, as shown in the following figure:
If no settlement ServiceImpl object information is found in the cache, the framework will continue the process of creating the object. SettleOrderServiceImpl add factory information to singletonFactories, as shown in the following figure:
SettleDetailServiceImpl and settleOrderServiceImpl are mapped in singletonFactories. Then, the settleOrderServiceImpl object must be initialized. The settleOrderServiceImpl has a property value, SettleDetailService. The SettleDetailServiceImpl framework tries to find a SettleDetailServiceImpl that meets the requirements of the two factory objects, but only the mapping between SettleDetailServiceImpl and settleOrderServiceImpl is stored in the framework. When the framework settleDetilServiceImpl object information to try again, back to call singletonFactory. GetObject () method and join the earlySingletonObjects will create good object information, The settleDetailServiceImpl mapping is removed from the singletonFactories. A preloaded object is returned, as shown in the following figure:
SettleDetailServiceImpl Object information is set to the settlement ServiceImpl object by reflection, as shown in the following figure:
The SettleOrderService property of the settleOrderServiceImpl has been assigned, and a complete settleOrderServiceImpl object is created. After the settlement ServiceImpl object is created, the settlement ServiceImpL can be initialized, as shown in the following figure:
SettleOrderServiceImpl and settleDetailServiceImpl loop dependencies have been created. There are many details that I did not explain in detail. For example, when to empty earlySingletonObjects, settleOrderServiceImpl and settleDetailServiceImpl loop dependencies have been created. Interested partners can do their own research.
Steps to resolve cyclic dependencies:
- The framework instantiates the settleDetailServiceImpl object and adds the factory object information to singtonFactories
- When a settleDetailServiceImpl object is initialized, it depends on the settleOrderServiceImpl object. The settlement ServiceImpL is created first
- To create a settleOrderServiceImpl object, instantiate it and add the settleOrderServiceImpl factory object to singletonFactories
- When the Settlement ServiceImpl is initialized, the settlement ServiceImpl depends on the Settlement ServiceImpl object. The settlement ServiceImpl framework attempts to resolve the problem
- SettleDetailServiceImpl (beanFactory#getObject) Add the settleSettleServiceImpl object to earlySingletonFactories and remove the factory object in singletonFactories
- SettleDetailServiceImpl is set to the Settlement ServiceImpl by reflection. Then, the Settlement ServiceImpL has been instantiated and initialized
- The settlement ServiceImpL framework uses reflection to set the entire Settlement ServiceImpl into a Settlement ServiceImpl object. At this time, the Settlement ServiceImpl is initialized
- SettleDetailServiceImpl and settleOrderServiceImpl are instantiated and initialized. Then, both objects are created
All of the above steps are the Spring framework’s solution to loop dependency, and the key ones are singletonObjects, earlySingletonObjects, and singletonFactories. Here we can probably understand the role of the three maps, to solve the puzzle of the role of the three maps at the beginning of the article.
Reflection:
Through the above analysis, we may be more or less to solve the dependency of the loop some of their own understanding. The author is due to the change of requirements and the lack of preciseness in writing code, resulting in the generation of loop dependence, although the framework provides us with a solution to the loop, but if we have enough understanding of the requirements in the early stage or a more reasonable design, maybe we can avoid these problems.