Creation is not easy, reprint please indicate the author: gold @ small xi son + source link ~

If you want to learn more about Spring source code, click to go to the rest of the line-by-line parsing Spring series

One, foreword

This post is about how spring solves the problem of circular dependencies.

What is circular dependency

First of all, what is a circular dependency? Here’s a simple example:

@Service
public class A {
    @Autowired
    private B b;
}
@Service
public class B {
    @Autowired
    private A a;
}
Copy the code

In this example, we declare two beans, a and B, and a needs to be injected into A and B needs to be injected into a.

Combining our knowledge of the bean lifecycle from the previous blog post, let’s simulate the process of creating these two beans:

Without the caching design, the branch shown by our dotted line would never have been reached, resulting in an unresolvable circular dependency problem….

Three, level 3 cache design

1. Solve circular dependency on your own

Now, if we were spring architects, how would we solve this circular dependency problem?

1.1. Process design

First, to solve this problem, our goal should be to switch from the previous cascaded infinite creation process to, that is, our process should be as follows:

In other words, we need to be able to get an instance of A after the instance of B is created, when we inject A, so that we can break the situation of creating an infinite number of instances.

The initialization process for instance B is triggered by dependency injection in the populateBean method after instance A is created. So if we want to get an instance of A during B instantiation, we must expose the instance of A after createBeanInstance is created and before the populateBean method is called, so that B can get it through getBean! (Students think seriously about this process, under the existing process transformation, is it only able to operate in this way? First think about this process, and then to combine with Spring source verification, this piece of knowledge you want to forget to forget)

Then combined with our ideas, let’s modify the flowchart again:

1.2. Pseudo code implementation

The process has been designed, so we can actually write the pseudo code of the process (the pseudo code does not write the lock process) :

// The map is initialized
private Map<String, Object> singleMap = new ConcurrentHashMap<>(16);
// Cache map
private Map<String, Object> cacheMap = new ConcurrentHashMap<>(16);

protected Object getBean(final String beanName) {
    // Let's see if the target bean is fully initialized
    Object single = singleMap.get(beanName);
    if(single ! =null) {
        return single;
    }
    // Check whether the target bean instance has been created
    single = cacheMap.get(beanName);
    if(single ! =null) {
        return single;
    }
    // Create an instance
    Object beanInstance = createBeanInstance(beanName);
    // After the instance is created, put it into the cache
    // Since the instance has already been created, the reference to the instance is no longer exposed
	// The following logic, such as attribute injection, is still done on this instance
    cacheMap.put(beanName, beanInstance);
    // Dependency injection, which triggers the dependent bean's getBean method
    populateBean(beanName, beanInstance);
    // Initialize the method call
    initializeBean(beanName, beanInstance);
    // Remove it from the cache and put it in the instance map
    singleMap.put(beanName, beanInstance);
    cacheMap.remove(beanName)
    
    return beanInstance;
}
Copy the code

As you can see, if we had implemented our own caching structure to solve the problem of circular dependencies, we would have needed only a two-tier structure, but Spring uses level 3 caching instead. What are the different considerations?

2. SpringThe source code

Now that we know how to solve circular dependencies, let’s take a look at the Spring source code and see if our analysis is correct.

Since we have covered the entire bean lifecycle in detail before, we will only pick the code segments related to the tertiary cache. We will skip a lot of code. If you are confused, you can review the 10,000word long article on the bean lifecycle.

2.1. SpringLevel 3 cache design

2.1.1. Level 3 cache source code

First, in our AbstractBeanFactory#doGetBean logic:

// Initialization triggers bean creation via getBean, and dependency injection eventually uses getBean to get an instance of the dependent bean
public Object getBean(String name) throws BeansException {
    return doGetBean(name, null.null.false);
}
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;

    // Get the bean instance
    Object sharedInstance = getSingleton(beanName);
    if(sharedInstance ! =null && args == null) {
        // beanFactory is related
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        // Skip some code
        // Create the bean logic
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }// Skip some code
    }
    // Skip some code
    // Return the bean instance
    return (T) bean;
}
Copy the code

As you can see, if we use getSingleton (beanName) direct access to the bean instance, will be directly return the bean instance, together we take a look at this method (the method belongs to DefaultSingletonBeanRegistry) :

// Level 1 cache, which caches normal bean instances
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// Level 2 cache, which caches bean instances that have not yet been dependency injected and initialized method calls
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// Level 3 cache, cache the ObjectFactory of the bean instance
/** Cache of singleton factories: bean name to ObjectFactory. */
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // First try to obtain the level 1 cache
    Object singletonObject = this.singletonObjects.get(beanName);
    // Failed to obtain, and the bean to be obtained is being created
    / / the first container initialization trigger getBean (A), the isSingletonCurrentlyInCreation judgment must be false
    // When this happens, we go through the process of creating the bean. Before creating the bean, we mark it as being created
    // After A is instantiated, the dependency injection to B triggers the instantiation of B. When B injects A, the getBean(A) is triggered again.
    / / the isSingletonCurrentlyInCreation will return true
    
    // When the current bean is being created, a circular dependency (or simultaneous fetching of the bean) occurs.
    // This is when you need to look at the second and third level cache
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        / / lock
        synchronized (this.singletonObjects) {
            // Obtain it from the level 2 cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                AllowEarlyReference is true if it is passed inObjectFactory<? > singletonFactory =this.singletonFactories.get(beanName);
                // Retrieve the ObjectFactory from the level 3 cache
                if(singletonFactory ! =null) {
                    // Get the bean instance from the ObjectFactory
                    singletonObject = singletonFactory.getObject();
                    // Put it into the level 2 cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // Delete from level 3 cache
                    Objectfactor #getObject is called only once for a singleton bean
                    // After the early bean instance is obtained, the bean instance is upgraded from the level 3 cache to the level 2 cache
                    this.singletonFactories.remove(beanName); }}}}// The bean instance is returned regardless of where it was obtained
    return singletonObject;
}
Copy the code

The first and second level caches are easy to understand, so you can actually think of them as the two maps in our pseudocode, but what about the third level cache? What is an ObjectFactory? Let’s take a look at the structure of the ObjectFactory:

@FunctionalInterface
public interface ObjectFactory<T> {
	// Well, it's simply a function interface that gets an instance
	T getObject(a) throws BeansException;
}
Copy the code

Let’s go back to the structure of the level-three cache. The level-two cache is put in the getSingleton method. This is not the same as what we analyzed earlier. Is it possible to infer that the bean instance is actually cached at level 3 after it is created? Let’s take a look at the bean instantiation code, focusing on some of the things we intentionally omitted from the previous article:

// The code is cut a lot, only the main logic is shown
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {

    // Create the bean instance
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    final Object bean = instanceWrapper.getWrappedInstance();
	// beanPostProcessor buried point call
    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
	
    // The important thing here is that if the bean is a singleton && allow circular dependencies && the current bean is being created
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // Add level 3 cache
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    Object exposedObject = bean;
    try {
        // Dependency injection
        populateBean(beanName, mbd, instanceWrapper);
        // Initialize the method call
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        throw newBeanCreationException(...) ; }if (earlySingletonExposure) {
        // If false is passed as the second argument, the value will not be taken from the level 3 cache
        Object earlySingletonReference = getSingleton(beanName, false);
        if(earlySingletonReference ! =null) {
            // If a value is found in the level 2 cache - a circular dependency is present
            if (exposedObject == bean) {
                // And initializeBean does not change the bean reference
                // Return the bean instance from the level 2 cacheexposedObject = earlySingletonReference; }}}try {
        // Register the destruction logic
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw newBeanCreationException(...) ; }return exposedObject;
}
Copy the code

After creating the bean instance, addSingletonFactory will be called to join the level 3 cache if the bean is a singleton && allows circular dependencies && the current bean is being created:

protected void addSingletonFactory(String beanName, ObjectFactory
        singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // Add level 3 cache
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName); }}}Copy the code

In other words, this section of our pseudocode has:

// Create an instance
Object beanInstance = createBeanInstance(beanName);
// After the instance is created, put it into the cache
// Since the instance has already been created, the reference to the instance is no longer exposed
// The following logic, such as attribute injection, is still done on this instance
cacheMap.put(beanName, beanInstance);
Copy the code

Then, when does the fully instantiated bean fit into our instance Map (level 1 cache) singletonObjects?

At this point we will return to the logic of calling the createBean method:

if (mbd.isSingleton()) {
    // Let's go back to this position
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throwex; }}); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }Copy the code

As you can see, our createBean creation logic is passed to the getSingleton method via a lamdba syntax.

public Object getSingleton(String beanName, ObjectFactory
        singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // The level 1 cache is not available
            // Note that this method marks that the bean is being created
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                // Call the external lamDBA, that is, createBean logic
                // Get the fully instantiated bean
                // Note that the instance of the bean is already in the level 2 or level 3 cache
                // Level-3 cache: this is where the bean instance is placed after it is created. If there is no cyclic dependency/concurrent fetching of the bean, it will remain in level-3 cache
                // Level 2 cache: If there is a circular dependency, the second time you enter getBean->getSingleton, the level 3 cache will be upgraded to level 2 cache
                singletonObject = singletonFactory.getObject();
                // Mark it
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throwex; }}catch (BeanCreationException ex) {
                throw ex;
            }
            finally {
                // Remove from the list being created, at which point the bean is either fully initialized
                // If the initialization fails, remove it
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // If a singleton bean is newly initialized, add it to the level 1 cacheaddSingleton(beanName, singletonObject); }}returnsingletonObject; }}Copy the code

The logic for adding singletonObjects to the instance Map (level 1 cache) is clearly in this addSingleton:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // This logic should come as no surprise
        // Insert the bean into the level-1 cache and remove it from level-2 and level-2 caches
        // It will be deleted anyway
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName); }}Copy the code

In other words, this piece of our pseudocode also has its counterpart in Spring, perfect:

// Initialize the method call
initializeBean(beanName, beanInstance);
// Remove it from the cache and put it in the instance map
singleMap.put(beanName, beanInstance);
cacheMap.remove(beanName)
Copy the code

In this way, Spring solves the problem of circular dependencies through caching design.

2.1.2. Flowchart for solving circular dependencies with level 3 caching

What, it’s still a little fuzzy after looking at the code? So let’s change our flow chart again and follow the spring process:

2.1.3. Level 3 caching resolves circular dependency pseudocode

If you still don’t think it’s clear, let’s put all the code related to spring’s level 3 cache together, and use pseudo code to make a method, you should feel more clear:

// Level 1 cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// Level 2 cache
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// Level 3 cache
private finalMap<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);

protected Object getBean(final String beanName) {
    / /! Here is the getSingleton logic!
    // Get it from the level 1 cache first
    Object single = singletonObjects.get(beanName);
    if(single ! =null) {
        return single;
    }
    // Retrieve it from the level 2 cache
    single = earlySingletonObjects.get(beanName);
    if(single ! =null) {
        return single;
    }
    // Retrieve the objectFactory from the level 3 cacheObjectFactory<? > objectFactory = singletonFactories.get(beanName);if(objectFactory ! =null) {
        single = objectFactory.get();
        // Upgrade to level 2 cache
        earlySingletonObjects.put(beanName, single);
        singletonFactories.remove(beanName);
        return single;
    }
    / /! That's the getSingleton logic!
    
    / /! The following is the doCreateBean logic
    // The cache is completely unreachable and needs to be created
    // Create an instance
    Object beanInstance = createBeanInstance(beanName);
    // After the instance is created, put it into the level 3 cache
    singletonFactories.put(beanName, () -> return beanInstance);
    // Dependency injection, which triggers the dependent bean's getBean method
    populateBean(beanName, beanInstance);
    // Initialize the method call
    initializeBean(beanName, beanInstance);
    
    // After dependency injection, if the level 2 cache has a value, it indicates a cyclic dependency
    // Select the bean instance from the level 2 cache
    Object earlySingletonReference = earlySingletonObjects.get(beanName);
    if(earlySingletonReference ! =null) {
        beanInstance = earlySingletonObject;
    }
    / /! This is the doCreateBean logic
    
    // Remove from the second and third caches and place in the first level cache
    singletonObjects.put(beanName, beanInstance);
    earlySingletonObjects.remove(beanName);
    singletonFactories.remove(beanName);
    
    return beanInstance;
}
Copy the code

After all the logic is put together, it will be a lot clearer. All you need to do is simulate it yourself, call getBean logic in populateBean again for dependency injection, and you should be clear.

2.1.4. Flag that the current bean is being created

In the process of wrapping the bean instance into an ObjectFactory and putting it into the level-three cache, there is a determination that the current bean is being created.

// The important thing here is that if the bean is a singleton && allow circular dependencies && the current bean is being created
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // Add level 3 cache
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Copy the code

We take a look at this isSingletonCurrentlyInCreation logic:

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

Can see the forehead, actually is to figure out whether the current beanName in this singletonsCurrentlyInCreation containers, so when the container what is the value in the operation?

GetSingleton (beanName, singletonFactory) beforeSingletonCreation and afterSingletonCreation

public Object getSingleton(String beanName, ObjectFactory
        singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // The level 1 cache is not available
            // Note that this method marks that the bean is being created
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            try {
                // Call the external lamDBA, that is, createBean logic
                singletonObject = singletonFactory.getObject();
                // Mark it
                newSingleton = true;
            }
            catch (BeanCreationException ex) {
                throw ex;
            }
            finally {
                // Remove from the list being created, at which point the bean is either fully initialized
                // If the initialization fails, remove it
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // If a singleton bean is newly initialized, add it to the level 1 cacheaddSingleton(beanName, singletonObject); }}returnsingletonObject; }}Copy the code

Let’s now look at the logic of these two methods:

protected void beforeSingletonCreation(String beanName) {
    / / add singletonsCurrentlyInCreation, because singletonsCurrentlyInCreation is a set
    // If this fails, create the bean twice
    // When this happens, the loop dependency exception is thrown
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw newBeanCurrentlyInCreationException(beanName); }}protected void afterSingletonCreation(String beanName) {
    / / delete from the singletonsCurrentlyInCreation
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); }}Copy the code

As you can see, we this two methods is mainly for singletonsCurrentlyInCreation manipulation, container, inCreationCheckExclusions this container can run through it, this name is a look at some of the white list configuration.

Need is the main beforeSingletonCreation here, if singletonsCurrentlyInCreation. Add (beanName) fails, Is sell BeanCurrentlyInCreationException, which represents the spring met can’t solve the problem of the circular dependencies, throws an exception interrupt initialization process at this time, after all, is not permitted to create the singleton bean twice.

2.2. Why is it designed as a three-level structure?

2.2.1. What are the problems with doing only two levels of caching?

At this point, it is clear that the level 3 cache design has successfully solved the circular dependency problem.

However, according to our own design ideas, it is clear that only two levels of caching can be solved, but Spring uses three levels of caching, is it to show off skills?

At this point, we need to take a closer look at the bean initialization process:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {
    // ...
    if (earlySingletonExposure) {
        // Put it into the level 3 cache
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        // Here the reference is replaced
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    // ...
    return exposedObject;
}
Copy the code

On closer inspection, it is possible for the initializeBean method to return a new object, thus replacing the bean instance created by createBeanInstance:

protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
    // Call the Aware interface
    invokeAwareMethods(beanName, bean);
    Object wrappedBean = bean;
    if (mbd == null| |! mbd.isSynthetic()) {/ / points
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        // Initialize the method
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw newBeanCreationException(...) ; }if (mbd == null| |! mbd.isSynthetic()) {/ / points
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}
Copy the code

As you can see, our postProcessBeforeInitialization and postProcessAfterInitialization buried points method are likely to our bean to replace.

So in combination with the whole process, since we put in the cache, the initializeBean method may have the situation of replacing the bean, if there are only two levels of caching:

This will result in an instance of A being injected into B that is inconsistent with the AA instance stored in singletonObjects, and then another instance that is injected into A will get an AA instance from singletonObjects, which is definitely not what is expected.

2.2.2. How does level 3 caching solve the problem

So how can this problem be solved?

At this point we will go back to where we added the level 3 cache. The second argument to addSingletonFactory is an ObjectFactory that will eventually be placed in the level 3 cache. Now let’s go back to where addSingletonFactory was called:

 // Add level 3 cache
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
Copy the code

If you’re familiar with the lamDBA syntax, getEarlyBeanReference is actually the logic of the getObject method that you put into the ObjectFactory in the level 3 cache. Let’s take a look at what this method does:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if(! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // A buried point method of beanPostProcessor is calledexposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); }}}// Return the buried point replacement bean
    return exposedObject;
}
Copy the code

Gee, there is also a buried point where you can replace the bean reference.

In order to solve the problem that initializeBean might replace bean reference, Spring designed this level 3 cache. In the level 3 cache, spring saved an ObjectFactory, which is actually a call to getEarlyBeanReference. A getEarlyBeanReference burying method is provided, which allows developers to replace beans that need to be replaced early.

For example, if you want to replace A with AA in the initializeBean method (this logic must be done through some beanPostProcessor), then your beanPostProcessor can also provide the getEarlyBeanReference method. InitializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean initializeBean A create -> inject B-> trigger B initialization -> inject A-> execute cache logic to get AA instance and put it into level 2 cache ->B initialization is complete -> return to A initialization logic by the following code:

protected Object doCreateBean(...). {
    populateBean(beanName, mbd, instanceWrapper);
    Object exposedObject = initializeBean(beanName, exposedObject, mbd);

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if(earlySingletonReference ! =null) {
            if (exposedObject == bean) {
                // Use the level-2 cache if it existsexposedObject = earlySingletonReference; }}}return exposedObject;
}
Copy the code

This ensures that the AA injected into the current bean and the AA instance in singletonObjects are the same object.

This ensures that the AA instance injected by B is the same as the final AA instance managed by Spring.

Here’s the whole process:

2.2.3. Practical application of level 3 cache

Since this level 3 cache is designed, there must be a real need for it, and we’ve looked at a bunch of them, so here’s an example of why Spring needs a level 3 cache.

Spring’s AOP capabilities, as we all know, are implemented by generating dynamic proxy classes, and we end up using proxy class instances instead of raw class instances. AOP proxy class to create, is buried in initializeBean method postProcessAfterInitialization point, We see getEarlyBeanReference and postProcessAfterInitialization directly buried the two dot (concrete class is AbstractAutoProxyCreator, after speaking the AOP is fine) :

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
    implements SmartInstantiationAwareBeanPostProcessor.BeanFactoryAware {
    
    private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
    // If a cyclic dependency occurs, getEarlyBeanReference will be called first
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // Insert earlyProxyReferences into the current class
        this.earlyProxyReferences.put(cacheKey, bean);
        // Return a proxy instance directly
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
    
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if(bean ! =null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            // If there is a loop dependency, the if block will not go in
            if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {// If there are no circular dependencies, the proxy class is created here
                returnwrapIfNecessary(bean, beanName, cacheKey); }}returnbean; }}Copy the code

In this way, Spring cleverly uses level 3 caching to solve the problem of different instances. Of course, if we need to develop our own functions such as proxies that might change bean references, we need to follow the buried logic of the getEarlyBeanReference method and learn to AbstractAutoProxyCreator to make Spring work as expected.

Problems that level 4 and level 3 caches cannot solve

1. Constructor cyclic dependencies

I just talked a lot about the implementation of level 3 caching and how it solves the problem of circular dependencies.

But is the use of level 3 caching the solution to all circular dependencies?

Of course not. There is one particular type of cyclic dependency that, due to Java language features, will never be resolved, and that is the constructor cyclic dependency.

For example, here are two classes:

public class A {
    private final B b;
    public A(final B b) {
        this.b = b; }}public class B {
    private final A a;
    public B(final A a) {
        this.a = a; }}Copy the code

Spring aside, is there any way you can successfully instantiate these two classes?

There will be no students say, this has what ugly me:

// You see, this is no good
final A a = new A(new B(a));
Copy the code

I’m sorry, this really doesn’t work, you can try it. Syntactically, the Java language does not allow uninitialized variables. We can only have an infinite set of dolls:

// This obviously doesn't solve the problem, it's an infinite loop of dolls
final A a = new A(new B(new A(new B(new A(newB(...) )))));Copy the code

So we shouldn’t force Spring to solve problems that we can’t solve

@Service
public class A {
    private final B b;
    public A(final B b) {
        this.b = b; }}@Service
public class B {
    private final A a;
    public B(final A a) {
        this.a = a; }}Copy the code

Error:

Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
Copy the code

2. SpringIs there really nothing you can do about constructor loop dependency?

Is Spring really helpless against this circular dependency? No, Spring also has a @lazy killer… We just need to make a small change to the two classes:

@Service
public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
    public void prt(a) {
        System.out.println("in a prt"); }}@Service
public class B {
    private final A a;
    public B(@Lazy final A a) {
        this.a = a;
    }
    public void prt(a) { a.prt(); }}/ / start
@Test
public void test(a) {
    applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    B bean = applicationContext.getBean(B.class);
    bean.prt();
}
Copy the code

All said successful, the results of the operation students can guess it:

in a prt
Copy the code

(Students can also try it by themselves

3. @LazyThe principle of

At this point we have to think about how Spring uses @Lazy to get around the infinite doll problem we just couldn’t solve.

Because it involves no fine before when the parameters of the injection parameter parsing problem, I will not take you step by step from the entrance into the, came here directly to the target code DefaultListableBeanFactory# resolveDependency:

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
                                @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	/ / skip...
    // This is where we get our dependency
    // Try to get a lazy loading agent
    Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
        descriptor, requestingBeanName);
    if (result == null) {
        // If you don't get the lazy loading proxy, you go directly to get the bean instance, which will eventually call getBean
        result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
    }
    return result;
}
Copy the code

We take a look at this getLazyResolutionProxyIfNecessary directly, this method is to obtain LazyProxy place:

public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {

    @Override
    @Nullable
    public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
        // If it is lazily loaded, build a lazily loaded agent
        return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
    }
	// The @lazy annotation is used to determine whether the load is Lazy
    protected boolean isLazy(DependencyDescriptor descriptor) {
        for (Annotation ann : descriptor.getAnnotations()) {
            Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
            if(lazy ! =null && lazy.value()) {
                return true;
            }
        }
        MethodParameter methodParam = descriptor.getMethodParameter();
        if(methodParam ! =null) {
            Method method = methodParam.getMethod();
            if (method == null || void.class == method.getReturnType()) {
                Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
                if(lazy ! =null && lazy.value()) {
                    return true; }}}return false;
    }

    protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
        final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
        // Construct a TargetSource
        TargetSource ts = new TargetSource() {
            @Override
            publicClass<? > getTargetClass() {return descriptor.getDependencyType();
            }
            @Override
            public boolean isStatic(a) {
                return false;
            }
            @Override
            public Object getTarget(a) {
                GetTarget = getTarget = getTarget = getTarget = getTarget = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean = getBean
                Object target = beanFactory.doResolveDependency(descriptor, beanName, null.null);
                if (target == null) { Class<? > type = getTargetClass();if (Map.class == type) {
                        return Collections.emptyMap();
                    }
                    else if (List.class == type) {
                        return Collections.emptyList();
                    }
                    else if (Set.class == type || Collection.class == type) {
                        return Collections.emptySet();
                    }
                    throw newNoSuchBeanDefinitionException(...) ; }return target;
            }
            @Override
            public void releaseTarget(Object target) {}};// Create agent factory ProxyFactory
        ProxyFactory pf = newProxyFactory(); pf.setTargetSource(ts); Class<? > dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {
            pf.addInterface(dependencyType);
        }
        // Create a return proxy class
        returnpf.getProxy(beanFactory.getBeanClassLoader()); }}Copy the code

Students may not be familiar with TargetSource and ProxyFactory, but that doesn’t stop us from understanding the logic.

From the source code, we can see that with the @Lazy dependency, we are actually returning a proxy class (hereafter called LazyProxy) rather than actually getting the target bean injection through getBean. The actual logic for getting the bean is encapsulated in the getTarget method of the TargetSource class, which is eventually used to generate the LazyProxy, so can we speculate, LazyProxy should hold this TargetSource object.

In our lazy loading semantics, we don’t inject/initialize the property until we actually use the bean (call a method on the bean).

When B is created, instead of calling getBean(“a”) to get the parameters of the constructor, it generates a LazyProxy to take the parameters of B’s constructor. GetTarget in the TargetSource is called getBean(” A “), where A is already instantiated, so there is no circular dependency problem.

4. Pseudo code description

Once again, we can describe this process in pseudocode, which we can describe directly with static proxies:

public class A {
    private final B b;
    public A(final B b) {
        this.b = b;
    }
    public void prt(a) {
        System.out.println("in a prt"); }}public class B {
    private final A a;
    public B(final A a) {
        this.a = a;
    }
    public void prt(a) { a.prt(); }}// A lazy loading proxy class
public class LazyProxyA extends A {
    private A source;
    private final Map<String, Object> ioc;
    private final String beanName;
    public LazyProxyA(Map<String, Object> ioc, String beanName) {
        super(null);
        this.ioc = ioc;
        this.beanName = beanName;
    }
    @Override
    public void prt(a) {
        if (source == null) { source = (A) ioc.get(beanName); } source.prt(); }}Copy the code

Then the whole initialization process can be described simply as:

Map<String, Object> ioc = new HashMap<>();
void init(a) {
    B b = new B(new LazyProxyA(ioc, "a"));
    ioc.put("b", b);
    A a = new A((B)ioc.get("b"));
    ioc.put("a", a);
}
Copy the code

Let’s also run a simulation:

void test(a) {
    // Container initialization
    init();
    B b = (B)ioc.get("b");
    b.prt();
}
Copy the code

Of course it can print successfully:

in a prt
Copy the code

Six, summarized

Spring provides a way to solve the problem of circular dependencies by designing a cache. The three-level cache is designed to solve the problem that when an instance is put into the cache during bean initialization, the reference to the instance may be replaced when the initializeBean method is called.

The three-level cache design is not able to resolve the constructor’s circular dependency, which is a Constraint of the Java language. Spring, however, provides a way around this restriction by using @lazy, so that the constructor’s cyclic dependency can be resolved in certain cases (with an injection in the loop annotated with @lazy).

(whisper BB, the next update should be slower…

Creation is not easy, reprint please indicate the author: gold @ small xi son + source link ~

If you want to learn more about Spring source code, click to go to the rest of the line-by-line parsing Spring series

٩ (* ఠ O ఠ) = 3 ⁼ after ₌ ₃ ⁼ after ₌ ₃ ⁼ after ₌ ₃ du la la la la…

Here is the new blogger Shiko, all the big guys have seen this, click “like” on the upper left corner before you go ~~