Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to parse the implementation of Spring circular dependency source code

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

How to analyze the Spring loop dependent source code implementation, I believe that many inexperienced people do not know what to do, so this paper summarizes the causes of the problem and solutions, through this article I hope you can solve this problem.

In order to let readers know more about Spring to solve the problem of circular dependency, I decided to analyze Spring to solve the problem of circular dependency. 2. What is circular dependency

The straightforward point of circular dependency occurs in two classes, you quote me, and I quote your state, as shown in the figure:

Schematic diagram of circular dependency 3. If you do not rely on Spring to solve circular dependency, how to solve it

The above figure is an example, assuming that we can create an AService, put it into a cache, and then inject attributes! Every time you inject an attribute, the required attribute value is fetched from the cache, so why not create it in the cache? As shown in the figure:

Summarize the above process:

After the AService is created, it adds itself to the second-level cache, and then starts to inject attributes to find that AService depends on BService, so first query whether there is data in the first-level cache, query the second-level cache if there is no first-level cache, query the second-level cache if there is no, create none in the BService cache, instantiate the BService, and then inject internal attributes! When injecting internal attributes, it is found that it depends on AService, so first query whether there is data in the first-level cache, query the second-level cache without querying the second-level cache, return it and create it without it. Obviously, the second-level cache contains data. So the AService is extracted from the secondary cache and injected into the BService. After the BService is created, move itself from the secondary cache to the primary cache, and return. After AService gets the BService, it injects it into its own attribute and removes itself from the second-level cache, and returns AService! At this point, the circular dependency creation is complete!

So with the above ideas, how can we implement our logic in code?

Fourth, if you do not rely on Spring to solve the circular dependency, how to solve the problem

First of all, we definitely want to define an annotation like @ Autowired, which we call @ MyAutowired.

Package simulation.annotations

Import java.lang.annotation.*

/ * *

* Custom injection annotations are equivalent to @ Autowired of Spring

* @ author huangfu

, /

@ Documented

@ Retention (RetentionPolicy.RUNTIME)

@ Target (ElementType.FIELD)

@ Inherited

Public @ interface MyAutowired {

}

Then we need to simulate a circular reference

Package simulation.service

Import simulation.annotations.MyAutowired

Public class AService {

@ MyAutowired

Private BService bService

}

Package simulation.service

Import simulation.annotations.MyAutowired

Public class BService {

@ MyAutowired

Private AService aService

}

Above, we define a circular reference, AService references BService; and BService references AService, standard circular references

Then, according to the train of thought mentioned in (3), we go to solve the problem with code.

Package simulation

Import simulation.annotations.MyAutowired

Import simulation.service.AService

Import java.lang.reflect.Field

Import java.util.Arrays

Import java.util.HashMap

Import java.util.List

Import java.util.Map

Import java.util.stream.Collectors

/ * *

* simulate Spring to solve the problem of circular dependency

* @ author huangfu

, /

Public class DebugTest {

/ * *

* has been fully created

, /

Private final Map singletonObject = new HashMap (8)

/ * *

* create half but no attribute injection

, /

Private final Map earlySingletonObjects = new HashMap (8)

Public static void main (String [] args) throws IllegalAccessException, InstantiationException {

DebugTest debugTest = new DebugTest ()

AService bean = debugTest.getBean (AService.class)

System.out.println (bean)

}

/ * *

* get a bean object

* @ param tClass

* @ return

, /

Public T getBean (Class tClass) throws InstantiationException, IllegalAccessException {

/ / first query whether there is any data in the first-level cache

String beanName = getBeanName (tClass)

Object object = singletonObject.get (beanName)

/ / the first-level cache is not querying whether there is data in the secondary cache.

If (object = = null) {

Object = earlySingletonObjects.get (beanName)

If (object = = null) {

/ / create a class without either cache

Object = createBean (tClass,beanName)

}

}

Return (T) object

}

/ * *

* create a bean

* @ param tClass

* @ param beanName

* @ return

, /

Public Object createBean (Class tClass,String beanName) throws IllegalAccessException, InstantiationException {

/ / reflection creates an object

Object newInstance = tClass.newInstance ()

/ / put it in the secondary cache after instantiation

EarlySingletonObjects.put (beanName,newInstance)

/ / start populating properties

PopulateBean (newInstance)

/ / transfer from the collection in the authoring to the entire collection after the completion of the fill

EarlySingletonObjects.remove (beanName)

SingletonObject.put (beanName,newInstance)

Return newInstance

}

/ * *

* fill attributes

, /

Public void populateBean (Object object) throws InstantiationException, IllegalAccessException {

/ / get all attributes with @ MyAutowired annotations added

List autowiredFields = getAutowiredField (object.getClass ())

For (Field field: autowiredFields) {

/ / start injection

DoPopulateBean (object, field)

}

}

/ * *

* start injecting objects

* @ param object

* @ param field

, /

Public void doPopulateBean (Object object, Field field) throws IllegalAccessException, InstantiationException {

/ / recall the acquisition logic

Object target = getBean (field.getType ())

Field.setAccessible (true)

/ / reflection injection

Field.set (object,target)

}

/ * *

* get the attributes that are identified and automatically injected

* @ param tClass

* @ return

, /

Private List getAutowiredField (Class tClass) {

Field [] declaredFields = tClass.getDeclaredFields ()

Return Arrays.stream (declaredFields) .filter (field->

Ield.isAnnotationPresent (MyAutowired.class)) .resume (Collectors.toList ())

}

/ * *

* get the class name

* @ param tClass

* @ return

, /

Public String getBeanName (Class tClass) {

Return tClass.getSimpleName ()

}

}

Result

Image-20200729225238673

From the above result diagram, we have solved the circular dependency, in fact, the solution of Spring is similar to our handwritten one, but Spring, as an ecology, its design and coding are also extremely thoughtful. Although we write this is similar to the original idea of Spring, what problems will arise?

Fifth, what are the defects in the way you realize it?

We are now directly injected into the object of the class, suppose we change the logic, if the target object we inject is an object that needs to be proxied (for example, the method is proxied by AOP), there is nothing we can do about it. Of course, we can re-create it to determine whether we need to add a proxy, of course, this is a solution, but for Spring. His original intention is to go to aop in the last few steps of the bean life cycle, and then finish the proxy logic of the object when it is injected, which is obviously not in line with its design philosophy, so how does Spring solve it?

6. How to solve the circular dependency in Spring?

First, we need to find out where the class is instantiated, because only when instantiated will the injection logic be executed!

Entry method:

Org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

Org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

@ Override

Public void preInstantiateSingletons () throws BeansException {

/ / traverse a copy to allow the use of the init method, which in turn registers the new bean definition.

/ / although this may not be part of the regular factory bootstrap program, it works properly.

/ / get all the bean name here

List beanNames = new ArrayList (this.beanDefinitionNames)

/ / triggers initialization of all non-lazy singleton bean.

For (String beanName: beanNames) {

/ / get the detailed definition of this class

RootBeanDefinition bd = getMergedLocalBeanDefinition (beanName)

/ / the first instantiation condition is not an abstract class, the second is a singleton class, the third is not a lazily loaded class

If (! bd.isAbstract () & & bd.isSingleton () & &! bd.isLazyInit ()) {

/ / Ooh-ho-ho

If (isFactoryBean (beanName)) {

.... Ignore unnecessary code, normal bean initialization will not go here.

} else {

/ / ordinary bean here is to re-create the entity object of Spring bean, and here is the most important logic we explore

GetBean (beanName)

}

}

}

. Ignore. Ignore.

}

}

This step is mainly getBean. Just imagine, according to the naming convention of Spring, why is it called getBean when you are creating Bean here? He must have done this for a reason, and he named it because, during attribute injection, he found that relying on a property would not be created immediately, but would call this method to get it again and not to create it again! It doesn't matter if you don't understand, you remember this place and look down! The method goes to getBean-- > doGetBean

Protected T doGetBean (final String name, @ Nullable final Class requiredType)

@ Nullable final Object [] args, boolean typeCheckOnly) throws BeansException {

Final String beanName = transformedBeanName (name)

Object bean

/ / check whether there are singletons registered manually in the singleton cache.

/ / check whether the singleton bean object exists in the first-level cache

/ / when the first-level cache does not exist but the current bean is in the state of creation (instantiation is completed but not initialized), check the second-level cache object and return if there is one.

/ / when the level 2 cache does not check the level 3 cache, call the callback method of the anonymous inner class of the level 3 cache to get the bean object, place it in the level 2 cache, delete the data in the level 3 cache and return the current bean

/ / the reason for caching from the third level is that if the class is a dependent class and a proxy is set, then what is obtained inside the method is the proxy object, which ensures that when injected, the proxy object is obtained for the first time

/ / in fact, if it is a circular reference and the third-level cache already exists when the referenced object is reinjected with attributes, the factory object of the third-level cache will be used to return the bean as a proxy when it should be a proxy. If there is no proxy, it will be returned directly.

Object sharedInstance = getSingleton (beanName)

/ / when circular dependency occurs, the data returned by querying this object for the first time must be null

If (sharedInstance! = null & & args = = null) {

.... Ignore unnecessary code.

} else {

.... Ignore unnecessary code.

Try {

Final RootBeanDefinition mbd = getMergedLocalBeanDefinition (beanName)

.... Ignore unnecessary code, here mainly make some judgments, such as instantiation dependency (@ dependsOn) and so on.

/ / create a bean instance. This is a real method to create a bean instance. If not, add the bean to the callback that you are creating and then go to create bean.

If (mbd.isSingleton ()) {

/ / this method is very important, and the following things will be done inside the method:

/ / 1. Determine whether there is bean in the current level 1 cache.

/ / 2. Do not create a method for the callback method (createBean) in the callback java8, add it to the first-level cache and return bean.

/ / 3. If the first-level cache exists, the bean will be returned directly.

SharedInstance = getSingleton (beanName, ()-> {

Try {

/ / here is the real logic for creating bean. The object is called back by the {@ link # getSingleton} method to follow the logic that actually executes the creation of bean.

Return createBean (beanName, mbd, args)

}

Catch (BeansException ex) {

/ / explicitly delete the instance from the singleton cache: it may already be there

/ / rush through the creation process to allow circular reference resolution.

/ / all bean that received a temporary reference to the bean is also deleted.

DestroySingleton (beanName)

Throw ex

}

});

Bean = getObjectForBeanInstance (sharedInstance, name, beanName, mbd)

} else if (mbd.isPrototype ()) {

.... Ignore unnecessary code.

} else {

.... Ignore unnecessary code.

}

}

Catch (BeansException ex) {

CleanupAfterBeanCreationFailure (beanName)

Throw ex

}

}

If (requiredType! = null & &! requiredType.isInstance (bean)) {

.... Ignore unnecessary code.

}

Return (T) bean

}

I handwritten the code to solve the circular dependency before. Is this familiar? This is to find the corresponding bean in the cache, return it directly when the cache exists, and create it only when it is not available! I believe that smart you must be thoughtful! This is extremely important. Let's go into createBean and protected Object createBean (String beanName, RootBeanDefinition mbd, @ Nullable Object [] args).

Throws BeanCreationException {

.... Ignore unnecessary code.

Try {

/ / here comes the real way to work. Ha. Create bean by reflection.

Object beanInstance = doCreateBean (beanName, mbdToUse, args)

/ /.... Ignore unnecessary code.

Return beanInstance

}

Catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {

/ / previously detected exception with correct bean creation context

/ / or illegal singleton status, which can be communicated to DefaultSingletonBeanRegistry at most.

Throw ex

}

Catch (Throwable ex) {

Throw new BeanCreationException (

MbdToUse.getResourceDescription (), beanName, "Unexpected exception during bean creation", ex)

}

}

Enter protected Object doCreateBean into doCreateBean (final String beanName, final RootBeanDefinition mbd, final @ Nullable Object [] args)

Throws BeanCreationException {

/ / instantiate bean.

BeanWrapper instanceWrapper = null

If (mbd.isSingleton ()) {

InstanceWrapper = this.factoryBeanInstanceCache.remove (beanName)

}

If (instanceWrapper = = null) {

/ / start to create the logic of the bean. In fact, the class has been instantiated here, except that a wrapper object is returned, and the instantiated object exists inside the wrapper object.

InstanceWrapper = createBeanInstance (beanName, mbd, args)

}

/ / get the previously created bean

Final Object bean = instanceWrapper.getWrappedInstance ()

.... Ignore unnecessary code.

/ / determine whether the current object is singleton, whether the circular reference is being created and whether these conditions are met before it will be placed in the third-level cache.

Boolean earlySingletonExposure = (mbd.isSingleton ()) & & this.allowCircularReferences

& & isSingletonCurrentlyInCreation (beanName))

If (earlySingletonExposure) {

.... Ignore unnecessary code.

/ / this method places the bean of the current instance number into the beanName-> bean wrapper object like the kv key-value pair stored in the third-level cache.

/ / when setting the purpose of this method, Spring is designed to expect Spring to operate on the proxy object after bean instantiation, rather than to determine whether it is a proxy object when it is re-created.

/ / but in fact, if a circular reference occurs, the dependent class will be created in advance and injected into the target class to ensure that an actual proxy object is injected

/ / so Spring came to steal the day for a change.

/ / when you need to inject later, you only need to return the data through the factory method. You can do proxy-related operations in the factory. After performing the proxy operation, the object is returned.

/ / in line with the expectations achieved in the later steps of the Spring life cycle in order to ensure the wrapper of the proxy object at the time of Springbean design.

/ / this step will also delete the secondary cached data

AddSingletonFactory (beanName, ()-> getEarlyBeanReference (beanName, mbd, bean))

}

/ / initialize the bean instance.

Object exposedObject = bean

Try {

/ / populate the internal attributes

/ / ☆ solves the problem of circular dependency, where the logic of automatic injection occurs

PopulateBean (beanName, mbd, instanceWrapper)

/ / perform initialization logic and lifecycle callback

ExposedObject = initializeBean (beanName, exposedObject, mbd)

}

Catch (Throwable ex) {

.... Ignore unnecessary code.

}

If (earlySingletonExposure) {

.... Ignore unnecessary code.

}

.... Ignore unnecessary code.

Return exposedObject

}

Go to the populateBean method, where attribute injection is performed, and circular dependencies are also solved! Protected void populateBean (String beanName, RootBeanDefinition mbd, @ Nullable BeanWrapper bw) {

.... Ignore unnecessary code.

/ / give any InstantiationAwareBeanPostProcessors modification opportunity

/ / the status of the Bean before setting the property. For example, you can use it

/ / support field injection method.

Boolean continueWithPropertyPopulation = true

If (! mbd.isSynthetic () & & hasInstantiationAwareBeanPostProcessors ()) {

For (BeanPostProcessor bp: getBeanPostProcessors ()) {

.... Ignore unnecessary code.

}

}

If (! continueWithPropertyPopulation) {

Return

}

.... Ignore unnecessary code.

If (hasInstAwareBpps) {

If (pvs = = null) {

Pvs = mbd.getPropertyValues ()

}

For (BeanPostProcessor bp: getBeanPostProcessors ()) {

If (bp instanceof InstantiationAwareBeanPostProcessor) {

InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp

/ / because it is automatically injected using @ Autowired annotations

/ / so Spring uses AutowiredAnnotationBeanPostProcessor.postProcessProperties to handle automatic injection

/ / as a matter of fact, this step will do injection processing, and this is also the object we focus on.

PropertyValues pvsToUse = ibp.postProcessProperties (pvs, bw.getWrappedInstance (), beanName)

.... Ignore unnecessary code.

}

}

}

If (needsDepCheck) {

.... Ignore unnecessary code.

}

If (pvs! = null) {

/ / start setting attribute value mbd is dependent bean

ApplyPropertyValues (beanName, mbd, bw, pvs)

}

}

Enter into AutowiredAnnotationBeanPostProcessor.postProcessProperties@Override

Public PropertyValues postProcessProperties (PropertyValues pvs, Object bean, String beanName) {

.... Ignore unnecessary code.

Try {

/ / injection logic

Metadata.inject (bean, beanName, pvs)

} catch (BeanCreationException ex) {

Throw ex

} catch (Throwable ex) {

Throw new BeanCreationException (beanName, "Injection of autowired dependencies failed", ex)

}

Return pvs

}

Go to injectpublic void inject (Object target, @ Nullable String beanName, @ Nullable PropertyValues pvs) throws Throwable {

Collection checkedElements = this.checkedElements

Collection elementsToIterate = (checkedElements! = null? CheckedElements: this.injectedElements)

If (! elementsToIterate.isEmpty ()) {

For (InjectedElement element: elementsToIterate) {

.... Ignore unnecessary code.

/ / the actual code that occurs in the injection logic uses AutowiredFieldElement.inject because it is attribute injection.

Element.inject (target, beanName, pvs)

}

}

}

Go to org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#injectprotected void inject (Object bean, @ Nullable String beanName, @ Nullable PropertyValues pvs) throws Throwable {

/ / get the attribute objects that need to be injected

Field field = (Field) this.member

Object value

. Ignore unnecessary code.

Else {

. Ignore unnecessary code.

Try {

/ / the code that really solves the dependency, and finds the code that creates the dependency

Value = beanFactory.resolveDependency (desc, beanName, autowiredBeanNames, typeConverter)

}

Catch (BeansException ex) {

Throw new UnsatisfiedDependencyException (null, beanName, new InjectionPoint (field), ex)

}

. Ignore unnecessary code.

}

If (value! = null) {

/ / injection logic of reflection

ReflectionUtils.makeAccessible (field)

Field.set (bean, value)

}

}

At this time, I don't want to say that wo cao finally sees hope. Here, beanFactory.resolveDependency gets the object to be injected, and then injects it into the object through reflection. Do we only need to know the logic in beanFactory.resolveDependency to know the problem of circular dependency? We went in decisively and found that it was not over yet: public Object resolveDependency (DependencyDescriptor descriptor, @ Nullable String requestingBeanName)

@ Nullable Set autowiredBeanNames, @ Nullable TypeConverter typeConverter) throws BeansException {

. Ignore unnecessary code.

If (result = = null) {

/ / solving dependency is the way to do practical work.

Result = doResolveDependency (descriptor, requestingBeanName, autowiredBeanNames, typeConverter)

}

Return result

}

}

Enter into doResolveDependency

@ Nullable

Public Object doResolveDependency (DependencyDescriptor descriptor, @ Nullable String beanName)

@ Nullable Set autowiredBeanNames, @ Nullable TypeConverter typeConverter) throws BeansException {

. Ignore unnecessary code.

/ / query the data of the bean by type and name

Map matchingBeans = findAutowireCandidates (beanName, type, descriptor)

. Ignore unnecessary code.

/ / this step is to actually create a class that calls the getBean method to re-follow the above set of logic for creating bean.

If (instanceCandidate instanceof Class) {

InstanceCandidate = descriptor.resolveCandidate (autowiredBeanName, type, this)

}

. Ignore unnecessary code.

Return result

}

. Ignore unnecessary code.

}

Congratulations on coming to an end, let's get into the descriptor.resolveCandidate (autowiredBeanName, type, this) method:

Org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate

Public Object resolveCandidate (String beanName, Class requiredType, BeanFactory beanFactory)

Throws BeansException {

Return beanFactory.getBean (beanName)

}

Oh ho, gentBean, I believe you must have lost your memory. Haven't you seen it somewhere? Think about the place I asked you to remember above, whether it is also a getBean, yes, they are the same method, you will find that the attributes that need to be injected will also go through the above logic to complete the creation and acquisition of the property object, thus completing the whole circular dependency! Borrow a picture from the boss of YourBatman to summarize the whole logic of solving the three-tier cache.

VII. Summary

After reading the above, have you mastered how to parse the Spring circular dependency source code implementation? If you want to learn more skills or want to know more about it, you are welcome to follow the industry information channel, thank you for reading!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report