An interview question with an annual salary of 500000 looks not difficult, but 99 people are brushed out

Keywords: Java Spring Back-end

Today I want to talk about the problem of circular dependency in spring. Recently, a large number of fans have asked this question, which is often asked in high salary interviews.

On the issue of circular dependence, let's feel the serial gun and try whether you can pass the pass and deal with it easily.

  1. What is circular dependency?

  2. How to detect the existence of circular dependencies?

  3. How to solve circular dependency?

  4. Why can't the circular dependency problem be solved in the case of multiple cases?

  5. In the case of singleton, although circular dependency can be solved, are there other problems?

  6. Why use L3 cache to solve circular dependency? Can I throw the early bean s directly into the L2 cache?

The first four are ok, more than 80% of the people. The difficulty index of the last two is increasing, and the one in a thousand can answer. If you can answer, the interviewer will admire you very much.

Let's make breakthroughs one by one.

What is circular dependency?

This is well understood. Multiple bean s depend on each other to form a closed loop.

For example, A depends on B, B depends on C, and C depends on A.

The code indicates:

public class A{
    B b;
}
public class B{
    C c;
}
public class C{
    A a;
}

How to detect the existence of circular dependencies?

Detecting circular dependency is relatively simple. Use a list to record the beans being created. Before bean creation, go to the record to see if you are already in the list. If yes, it indicates that there is a circular dependency. If not, add it to the list. After bean creation, remove it from the list.

From the perspective of source code, spring will call the following methods when creating a singleton bean

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

singletonsCurrentlyInCreation is used to record the bean name list currently being created. this.singletonsCurrentlyInCreation.add(beanName) returns false, indicating that beanName is already in the current list. At this time, the circular dependency exception beancurrentyincreationexception will be thrown. The source code corresponding to this exception:

public BeanCurrentlyInCreationException(String beanName) {
        super(beanName,
                "Requested bean is currently in creation: Is there an unresolvable circular reference?");
    }

The above is the source code for detecting circular dependency of singleton beans. Let's take a look at non singleton beans.

Take prototype as an example. The source code is located in the org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean method. List the main codes:

//Check whether beanName exists in the bean list being created. If it exists, it indicates that there is a circular dependency, and throw an exception of circular dependency
if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}

//Determine whether the scope is prototype
if (mbd.isPrototype()) {
    Object prototypeInstance = null;
    try {
        //Put beanName in the list being created
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
        //Remove beanName from the list being created
        afterPrototypeCreation(beanName);
    }
}

How does Spring solve the problem of circular dependency

Let's take a look at this proposal first: Explain the bean life cycle

The main steps for spring to create bean s are as follows:

  1. Step 1: instantiate a bean, that is, call the constructor to create a bean instance

  2. Step 2: fill in the attributes and inject the dependent bean s, for example, through set and @ Autowired annotation

  3. Step 3: initialize the bean, such as calling the init method.

As can be seen from the above three steps, there are two situations for injecting dependent objects:

  1. Inject dependencies through the constructor in step 1

  2. Inject dependencies through step 2

Let's first look at how the constructor injects dependent beans. The following two beans are cyclic dependencies

@Component
public class ServiceA {
    private ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Component
public class ServiceB {
    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

It is easy to understand the situation of the constructor. When instantiating ServiceA, you need serviceb, while when instantiating serviceb, you need ServiceA. The constructor circular dependency cannot be solved. You can try to create the above two objects by coding, and you can't create them successfully!

Let's look at the non constructor injection of interdependent beans. Take the set injection as an example. Here are two singleton beans: serviceA and serviceB:

@Component
public class ServiceA {
    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}


@Component
public class ServiceB {
    private ServiceA serviceA;

    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

If we create the above two objects by hard coding, the process is as follows:

//Create serviceA
ServiceA serviceA = new ServiceA();
//Create serviceB
ServiceB serviceB = new ServiceB();
//Inject serviceA into serviceB
serviceB.setServiceA(serviceA);
//Inject serviceB into serviceA
serviceA.setServiceB(serviceB);

Since there is only one singleton bean in the spring container, there must be a cache in the spring container to store all the created singleton beans; Before obtaining the singleton bean, you can first find it in the cache, return it directly when it is found, and then create it if it cannot be found. After creation, you can throw it into the cache. You can use a map to store the singleton bean, such as the following

Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

Let's take a look at the process of creating the above two bean s with the set method in spring

1.spring Polling ready to create 2 bean: serviceA and serviceB
2.spring Container discovery singletonObjects Not in serviceA
3.call serviceA Constructor creation for serviceA example
4.serviceA Prepare to inject dependent objects and find the objects that need to be injected through setServiceB injection serviceB
5.serviceA towards spring Container lookup serviceB
6.spring Container discovery singletonObjects Not in serviceB
7.call serviceB Constructor creation for serviceB example
8.serviceB Prepare to inject dependent objects and find the objects that need to be injected through setServiceA injection serviceA
9.serviceB towards spring Container lookup serviceA
10.Now go to step 2 again

Lying trough, the process above is dead circulation. How can it end?

You can add an operation after step 3: throw the instantiated serviceA into singletonObjects. At this time, the problem is solved.

Spring also adopts a similar method, with a slight difference. It uses a cache, while spring uses a level 3 cache to solve this problem. Let's take a closer look.

Code corresponding to level 3 cache:

/** First level cache: cache of singleton bean  */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Second level cache: cache of early exposed bean s  */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Level 3 cache: cache of singleton bean factory  */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

Let's look at the specific process in spring. Let's analyze the source code together

At the beginning, get serviceA and call the following code

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                              @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    //1. Check whether this bean already exists in the cache
    Object sharedInstance = getSingleton(beanName); //@1
    if (sharedInstance != null && args == null) {
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }else {
        //If it does not exist in cache, prepare to create bean
        if (mbd.isSingleton()) {
            //2. Next, enter the creation process of the singleton bean
            sharedInstance = getSingleton(beanName, () -> {
                try {
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }
    return (T) bean;
}

@1: Check whether this bean already exists in the cache, as follows:

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

Then enter the following method and try to find bean s from the level 3 cache in turn. Note that the second parameter below will be found from the level 3 cache only when it is true. Otherwise, only the level 1 and level 2 caches will be found

//Alloweerlyreference: is it allowed to get bean s from singletonFactories in the third level cache through getObject
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //1. Find the first level cache first
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //2. From L2 cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //3. Not found in L2 cache  &&  When alloweerlyreference is true, find it from the L3 cache
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //The L3 cache returns a factory through which the created bean is obtained
                    singletonObject = singletonFactory.getObject();
                    //Throw the created bean into the L2 cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //Remove from L3 cache
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

At first, it must not be found in the three caches, and null will be returned. Then, the following code will be executed to create serviceA

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> { //@1
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
}

@1: Enter the getSingleton method. There are many codes in the getSingleton method. In order to facilitate your understanding, I have eliminated irrelevant codes, as follows:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //Before invoking the single instance bean, it is added to the list that is being created. As mentioned above, it is mainly used to detect circular dependency.
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;

            try {
                //Call factory to create bean
                singletonObject = singletonFactory.getObject();//@1
                newSingleton = true;
            }
            finally {
                 //Invoking a singleton bean before it is created is mainly removed from the list being created.
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                //Put the created singleton bean into the cache
                addSingleton(beanName, singletonObject);//@2
            }
        }
        return singletonObject;
    }
}

The above @ 1 and @ 2 are the key codes. Let's take a look at @ 1. This is an ObjectFactory type, which is passed in from the outside, as follows

The createBean in the red box will eventually call the following method

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

The main internal codes are as follows:

BeanWrapper instanceWrapper = null;

if (instanceWrapper == null) {
    //Instantiate serviceA through reflection call constructor
    instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//Variable bean: represents the bean example just created by the same constructor
final Object bean = instanceWrapper.getWrappedInstance();

//Judge whether it is necessary to expose the early bean. The condition is (whether it is a singleton bean or not)  &&  The current container allows circular dependencies  &&  Bean name exists in the bean name list being created)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                  isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    //If earlySingletonExposure is true, expose the earlier bean s through the following code
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//@1
}

Here we need to understand what early bean s are?

The bean just instantiated is an early bean. At this time, the bean has not been filled with properties, initialized, etc

@1: addSingletonFactory is used to expose early bean s, mainly throwing them into the level 3 cache. The code is as follows:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        //The bean does not exist in the level 1 cache
        if (!this.singletonObjects.containsKey(beanName)) {
            //Drop it into the level 3 cache
            this.singletonFactories.put(beanName, singletonFactory);
            //Don't pay attention to the next two lines of code
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

After the above method is executed, serviceA is thrown into the level 3 cache.

In the subsequent process, serviceA starts to inject dependent objects. If it finds that serviceB needs to be injected, it will obtain serviceB from the container, and the acquisition of serviceB will follow the same process as above. Instantiate serviceB, expose serviceB in advance, and then serviceB starts to inject dependent objects. serviceB finds that it needs to inject serviceA. At this time, it goes to the container to find serviceA, When you find serviceA, you will first find it in the cache and execute getSingleton("serviceA",true). At this time, you will follow the following code:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //1. Find the first level cache first
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            //2. From L2 cache
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                //3. Not found in L2 cache  &&  When alloweerlyreference is true, find it from the L3 cache
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //The L3 cache returns a factory through which the created bean is obtained
                    singletonObject = singletonFactory.getObject();
                    //Throw the created bean into the L2 cache
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    //Remove from L3 cache
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

After the above method is completed, serviceA will be put into the secondary cache earlySingletonObjects, and serviceA will be returned. At this time, serviceA in serviceB is injected successfully, serviceB continues to complete creation, and then returns itself to serviceA. At this time, serviceA injects serviceB through the set method.

After serviceA is created, addSingleton method will be called to add it to the cache. The code is as follows:

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        //Put the bean into the level 1 cache
        this.singletonObjects.put(beanName, singletonObject);
        //Remove it from the level 3 cache
        this.singletonFactories.remove(beanName);
        //Remove it from the level 2 cache
        this.earlySingletonObjects.remove(beanName);
    }
}

At this point, the cyclic dependency injection between serviceA and serviceB is completed.

Let's go through the whole process:

1.Get from container serviceA
2.The container tried to find from 3 caches serviceA,can't find
3.Ready to create serviceA
4.call serviceA Constructor creation for serviceA,obtain serviceA Instance, at this time serviceA The property has not been populated and no other initialization operations have been performed
5.Will the early serviceA Expose: throw it into the level 3 cache singletonFactories in
6.serviceA Prepare to fill the attribute, and find that injection is required serviceB,Then get from the container serviceB
7.The container tried to find from 3 caches serviceB,can't find
8.Ready to create serviceB
9.call serviceB Constructor creation for serviceB,obtain serviceB Instance, at this time serviceB The property has not been populated and no other initialization operations have been performed
10.Will the early serviceB Expose: throw it into the level 3 cache singletonFactories in
11.serviceB Prepare to fill the attribute, and find that injection is required serviceA,Then get from the container serviceA
12.The container tried to find from 3 caches serviceA,Found at this time serviceA In the level 3 cache, after processing, serviceA It is removed from the level 3 cache, then saved to the level 2 cache, and then returned to the serviceB,here serviceA adopt serviceB Medium setServiceA Method is injected into serviceB in
13.serviceB Continue to perform some subsequent operations, and finally complete the creation, and then call addSingleton Method, throw yourself into the level 1 cache and remove yourself from the level 2 and 3 caches
14.serviceB Return yourself to serviceA
15.serviceA adopt setServiceB Method will serviceB Inject it
16.serviceB Continue to perform some subsequent operations, and finally complete the creation,Then it calls addSingleton Method, throw yourself into the level 1 cache and remove yourself from the level 2 and 3 caches

Situations where circular dependency cannot be resolved

Only singleton beans will be exposed in advance through the L3 cache to solve the problem of circular dependency. Non singleton beans will be a new object every time they are obtained from the container and will be recreated. Therefore, non singleton beans will not be cached and will not be put into the L3 cache.

Then there will be the following situations to pay attention to.

Take the interdependence of two bean s as an example: serviceA and serviceB

Case 1

condition

serviceA: multiple cases

serviceB: multiple cases

result

At this time, the problem of circular dependency cannot be solved in any way, and an error will be reported in the end, because every time you get the dependent bean, it will be recreated.

Case 2

condition

serviceA: single case

serviceB: multiple cases

result

If the constructor is used to inject each other, the injection operation cannot be completed and an error will be reported.

If set injection is adopted and all bean s have not been created, an error will be reported if you get serviceB from the container. Why? Let's look at the process:

1.Get from container serviceB
2.serviceB Since there are multiple instances, there must be none in the cache
3.inspect serviceB Is created in the bean Name list, no
4.Ready to create serviceB
5.take serviceB Put in the file you are creating bean Name list
6.instantiation  serviceB(because serviceB It is multiple cases, so it will not be exposed in advance. It must be a single case.)
7.Ready to fill serviceB Property. It is found that injection is required serviceA
8.Find from container serviceA
9.Trying to find from level 3 cache serviceA,can't find
10.Ready to create serviceA
11.take serviceA Put in the file you are creating bean Name list
12.instantiation  serviceA
13.because serviceA It is a single case and will be early serviceA Expose it and throw it into the level 3 cache
14.Ready to fill serviceA Property, found that needs to be injected serviceB
15.Get from container serviceB
16.Look in the cache first serviceB,can't find
17.inspect serviceB Is created in the bean Name list,If it is found that it already exists, throw the exception of circular dependency

The source code of this demonstration is located at:

com.javacode2018.lesson003.demo2.CircleDependentTest#test2

Here's a question for you. If you don't get serviceB here, but get serviceA first, will an error be reported? Welcome to leave a message.

Discussion: why use level 3 cache

problem

If only the L2 cache is used, can the newly instantiated bean s be directly exposed to the L2 cache?

Let's draw a conclusion: No.

reason

This can solve the problem that the beans exposed to other dependencies in the early stage are inconsistent with the beans finally exposed.

If the newly instantiated bean is directly thrown into the L2 cache and exposed, if the bean object is changed later, for example, some interceptors may be added to wrap it as a proxy, then the exposed bean is different from the final bean. When exposing itself, it is an original object, In the end, it is a proxy object, which will eventually lead to the fact that the exposed bean and the final bean are not the same bean, which will have an unintended effect. The third level cache can find this problem and report an error.

Let's demonstrate the effect through code.

case

The following two bean s are interdependent. They are injected into each other through the set method, and there is an m1 method inside them to output a line of log.

Service1

package com.javacode2018.lesson003.demo2.test3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Service1 {
    public void m1() {
        System.out.println("Service1 m1");
    }

    private Service2 service2;

    @Autowired
    public void setService2(Service2 service2) {
        this.service2 = service2;
    }

}

Service2

package com.javacode2018.lesson003.demo2.test3;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Service2 {

    public void m1() {
        System.out.println("Service2 m1");
        this.service1.m1();//@1
    }

    private Service1 service1;

    @Autowired
    public void setService1(Service1 service1) {
        this.service1 = service1;
    }

    public Service1 getService1() {
        return service1;
    }
}

Note that @ 1 above, the m1 method of service1 will be called in the m1 method of service2.

demand

Add an interceptor on service1. It is required to output a line of log before calling any method of service1

Hello,service1

realization

Add a bean post processor to process the bean corresponding to service1, encapsulate it as a proxy and expose it.

package com.javacode2018.lesson003.demo2.test3;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
public class MethodBeforeInterceptor implements BeanPostProcessor {
    @Nullable
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("service1".equals(beanName)) {
            //To create an agent factory, you need to pass in the target object of the agent
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            //Adding a method prefix notification will call the before method in the notification before the method is executed.
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                    System.out.println("Hello,service1");
                }
            });
            //Return proxy object
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

The postProcessAfterInitialization method above will be called internally after service1 is initialized. It will process the service1 bean internally, return a proxy object, access the service1 by proxy, and export any method in service1 before it will output: Hello, service1.

ProxyFactory is used in the code. It doesn't matter if you're not familiar with it. I'll talk about aop in detail later.

A configuration class

@ComponentScan
public class MainConfig3 {

}

Let's have a test case

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    context.register(MainConfig3.class);
    context.refresh();
}

Operation: error reported

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'service1': Bean with name 'service1' has been injected into other beans [service2] 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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624)

It can be seen that it is the exception from AbstractAutowireCapableBeanFactory.java:624. Post this code for everyone to see:

if (earlySingletonExposure) {
    //@1
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        //@2
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        //@3
        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 " +
                                                           "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
        }
    }
}

The above code is mainly used to judge whether the bean exposed to others in the early stage is different from the final bean in the case of circular dependency, and an exception will be thrown.

Let's explain the above code through code level:

@1: Call the getSingleton(beanName, false) method. This method is used to get beans from the three-level cache. Note that the second parameter in this area is false. At this time, we will only try to get beans from the level 1 and level 2 cache. If we can get beans, what does it mean? This indicates that this bean already exists in the level 2 cache, and under what circumstances will there be a bean in the level 2 cache? This indicates that this bean has been obtained by others from the level 3 cache, and then moved from the level 3 cache to the level 2 cache, indicating that this early bean has been obtained by others through getSingleton(beanName, true)

@2: This place is used to judge whether the bean exposed earlier and the final spring container are still the same bean after the bean creation process. Our service1 above is proxied, so this place will return false and go to @ 3

@3: Allowerawinjectiondespirewrapping is used to control whether the beans exposed to others in the early stage can be wrapped in the later stage when circular dependency is allowed. Generally speaking, it means whether the beans used by others in the early stage are allowed to be inconsistent with the final beans. This value is false by default, indicating that it is not allowed, In other words, the beans you expose to others and your final beans need to be the same all the time. What you give others is 1. You can't change it to 2 later. It's different. You use a bird for me.

The service1 injected into service2 by the above code is an early service1, and finally the service1 in the spring container becomes a proxy object. The early and final objects are inconsistent, and allowwinjectiondespirewrapping is false, so an exception is reported.

So how to solve this problem:

Simply set allowerinjectiondespirewrapping to true. The following code is modified as follows:

@Test
public void test4() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    //Create a BeanFactory postprocessor: BeanFactory postprocessor
    context.addBeanFactoryPostProcessor(beanFactory -> {
        if (beanFactory instanceof DefaultListableBeanFactory) {
            //Set allowarinjectiondespitewrapping to true
            ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
        }
    });
    context.register(MainConfig3.class);
    context.refresh();

    System.out.println("Container initialization completed");
}

allowRawInjectionDespiteWrapping is set to true in the above code through a beanfactoryprocessor. An article will explain beanfactoryprocessor in detail later. At present, you only need to know that beanfactoryprocessor can be used to intervene in the creation process of BeanFactory before bean creation and modify some configurations in BeanFactory.

Output again

Container initialization completed

At this time, it is normal. Let's continue to see if the interceptor we added to service1 works. Add the following code to the above code:

//Get service1
Service1 service1 = context.getBean(Service1.class);
//Get service2
Service2 service2 = context.getBean(Service2.class);

System.out.println("----A-----");
service2.m1(); //@1
System.out.println("----B-----");
service1.m1(); //@2
System.out.println("----C-----");
System.out.println(service2.getService1() == service1);

In order to distinguish the results, several lines of logs in the format of - - are used to separate the output results. Run it and output:

Container initialization completed
----A-----
Service2 m1
Service1 m1
----B-----
Hello,service1
Service1 m1
----C-----
false

As can be seen from the output.

Corresponding output of service2.m1():

Service2 m1
Service1 m1

Corresponding output of service1.m1():

Hello,service1
Service1 m1

In the service2.m1 method, service1.m1 is called, and the interceptor doesn't work, but the service1.m1 method is invoked separately, but it works. It shows that the injected service1 in service2 is not a proxy object, so the function of the interceptor is not added, because service2 is injected with the early service1, and service1 is not yet a proxy object when injected. So there is no function in the interceptor.

Let's see that the output in the last line is false, indicating that service1 in service2 is indeed not an object with service1 in spring container.

ok, so this situation is not very surprising. How to solve this problem?

Since service1 is ultimately a proxy object, you must also be a proxy object when you expose it in advance and inject it into service2. You need to ensure that it is the same object to others.

How do you fix this? Continue to look at the source code of the exposed early bean s. Note that the following are the key points:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Note that there is a getEarlyBeanReference method. Let's see what this method does. The source code is as follows:

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;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

When obtaining beans from the level 3 cache, the above method will be called to obtain beans. In this method, we will check whether there is a processor such as SmartInstantiationAwareBeanPostProcessor in the container, and then call the getEarlyBeanReference method in this processor in turn. Then the idea comes. We can customize a SmartInstantiationAwareBeanPostProcessor, Then create an agent in its getEarlyBeanReference. Smart, let's try and change the MethodBeforeInterceptor code to the following:

@Component
public class MethodBeforeInterceptor implements SmartInstantiationAwareBeanPostProcessor {
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        if ("service1".equals(beanName)) {
            //To create an agent factory, you need to pass in the target object of the agent
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            //Adding a method prefix notification will call the before method in the notification before the method is executed.
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
                    System.out.println("Hello,service1");
                }
            });
            //Return proxy object
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

Corresponding test cases

@Test
public void test5() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
    System.out.println("Container initialization completed");

    //Get service1
    com.javacode2018.lesson003.demo2.test4.Service1  service1 = context.getBean(com.javacode2018.lesson003.demo2.test4.Service1.class);
    //Get service2
    com.javacode2018.lesson003.demo2.test4.Service2 service2 = context.getBean(com.javacode2018.lesson003.demo2.test4.Service2.class);

    System.out.println("----A-----");
    service2.m1(); //@1
    System.out.println("----B-----");
    service1.m1(); //@2
    System.out.println("----C-----");
    System.out.println(service2.getService1() == service1);
}

Run output

Container initialization completed
----A-----
Service2 m1
 Hello,service1
Service1 m1
----B-----
Hello,service1
Service1 m1
----C-----
true

The singleton bean solves the circular dependency. What are the problems?

In the case of circular dependency, because the early bean is injected, the early bean has not been filled with properties, initialization and other operations, that is, the bean has not been fully initialized. If it is used directly, there may be a risk of problems.

summary

So far, everyone must know the answer to the question of the first serial gun. If there is anything unclear, please leave a message

 

Posted by erasam on Wed, 17 Nov 2021 00:14:40 -0800