Before the order

Before, I was asked by many young brothers. In the interview process, I was often asked how Spring solved loop dependency. And why multi-level caching to handle cyclic dependencies? And so on the problem, the kind is quite many, their head is a little big; In recent days, the relevant resources are sorted out, and the process is described here;

How did the simulation loop dependency come about

Let’s start with an example (gargle with a pint of laobaigan) to simulate how cyclic dependence actually occurs;

1.1) Start by creating two test beans and an interface

/** * interface description: loop dependent test interface * <br /> * first one interface; PS: I have been used to abstract and simulate the flow direction of the process before doing something, then simulate the data flow direction, and finally complete the logic. * <p/> * Here is my personal opinion; A guy heard out of nowhere that every process should have its own interface, one interface for each class * If you develop multiple classes with one interface, he will treat you like a monster and tell you, "This is Spring standard"; An interface that corresponds to multiple classes will suffer a lot later; * I searched all the relevant materials of Spring, but I really didn't find this standard. I don't know if I didn't find the right place. Or later when the big loss of time to say *@author XXSD
 * @version 1.0.0
 * @date 2021/3/25 0025 下午 8:21
 */
public interface ICirulationDependencyTest {

    /** **@author : XXSD
    * @date: 2021/3/250025 8:22 PM */
    void demoTest(a);
}
Copy the code

Test class — A:

/** * Class description: Loop depends on the test Bean -- A **@author XXSD
 * @version 1.0.0
 * @date 2021/3/24 0024 下午 8:42
 */
public class CirulationDependencyTestBeanA implements ICirulationDependencyTest {

    @Autowired
    private CirulationDependencyTestBeanB cirulationDependencyTestBeanB;

    /** * loop dependency test Bean *@author : XXSD
    * @date: 2021/3/250025 9:06 PM */
    public CirulationDependencyTestBeanA(a) {
        System.out.println("CirulationDependencyTestBeanA -- instantiate");
    }

    @Override
    public void demoTest(a){
        System.out.println("Call the CirulationDependencyTestBeanA demoTest method"); }}Copy the code

Test class — B:

/** * Class description: Loop depends on the test Bean -- B **@author XXSD
 * @version 1.0.0
 * @date 2021/3/24 0024 下午 8:42
 */
public class CirulationDependencyTestBeanB implements ICirulationDependencyTest {
    @Autowired
    private CirulationDependencyTestBeanA cirulationDependencyTestBeanA;

    /** * loop dependency test Bean *@author : XXSD
    * @date: 2021/3/250025 9:07 PM */
    public CirulationDependencyTestBeanB(a) {
        System.out.println("CirulationDependencyTestBeanB -- instantiate");
    }

    @Override
    public void demoTest(a){
        System.out.println("Call the CirulationDependencyTestBeanB demoTest method"); }}Copy the code

1.2) Official simulation begins

/** * class description: loop dependency -- emulates **@author XXSD
 * @version 1.0.0
 * @date 2021/3/24 0024 下午 8:39
 */
public class CircularDependency {

    /** * Attribute description: Beandefinition (Beandefinition) container * <br /> * Here we simulate a container exactly like Spring for storing Beandefinition objects **@date : 2021/3/24 0024 下午 8:40
     */
    private static final Map<String, BeanDefinition> BEAN_DEFINITION_MAP = new ConcurrentHashMap<>(16);

    In Spring, the variable name of the level 1 cache container is: singletonObjects *@date : 2021/3/25 0025 下午 9:03
     */
    private static final Map<String, Object> LEVEL_ONE_CACHE_MAP = new ConcurrentHashMap<>(16);

    /** * Initializes * <br /> * puts test beans into virtual containers **@author : XXSD
     * @date : 2021/3/24 0024 下午 8:45
     */
    public CircularDependency(a) {
        /* * Here we initialize two Bean definitions * */
        RootBeanDefinition beanA = new RootBeanDefinition(CirulationDependencyTestBeanA.class);
        RootBeanDefinition beanB = new RootBeanDefinition(CirulationDependencyTestBeanB.class);
        /* * This simulates loading the Bean definition into the cache * note: this is loaded by scanning in Spring * */
        BEAN_DEFINITION_MAP.put("cirulationDependencyTestBeanA", beanA);
        BEAN_DEFINITION_MAP.put("cirulationDependencyTestBeanB", beanB);
    }

    /** * this emulates Spring's method of obtaining beans@author : XXSD
    * @date: 2021/3/250025 8:13 PM */
    public Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
        final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
        /* * Review the life cycle of a Bean: instantiate, attribute assignment, initialize, add to level 1 cache * Step 1: simulate instantiation; Get the Class and instantiate it by reflection; * In Spring, factory methods and optional parameterized constructors are also called during Bean instantiation. This is just simulation, so don't worry about the details; * * /

        finalClass<? > beanClass = beanDefinition.getBeanClass();final Object instance = beanClass.newInstance();
        /* * Step 2: Start simulating attribute assignment; First get all the attributes, through reflection to assign; * First check if @autowired * */ exists on the property
        final Field[] declaredFields = beanClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            final Autowired annotation = declaredField.getAnnotation(Autowired.class);
            if(annotation! =null) {/* * indicates that the attribute is required to be injected. * Enable access permission first; Call this method recursively to get the object to be injected; * Note: In Spring you can fetch not only by name, but also by type and constructor; (byName, byType, constructor) don't worry about the details here, just simulation; * * /
                declaredField.setAccessible(true); declaredField.set(instance, getBean(declaredField.getName())); }}/* * step 3: implement the specified method or rule, such as init-mthod etc. I'm going to skip this, I don't have to deal with it; * Step 4: Place in level 1 cache * */
        LEVEL_ONE_CACHE_MAP.put(beanName, instance);
        return instance;
    }

    /** *@author : XXSD
    * @date : 2021/3/25 0025 下午 8:12
    */
    public static void main(String[] values) throws InstantiationException, IllegalAccessException {
        final CircularDependency circularDependency = new CircularDependency();

        /* * Here we begin to simulate fetching the corresponding Beandefinition object from the cache * */
        final Set<String> beanDefinitonMapKeys = BEAN_DEFINITION_MAP.keySet();
        for (String beanDefinitonMapKey : beanDefinitonMapKeys) {
            /* * the Bean definition of object A is loaded first and then B * */circularDependency.getBean(beanDefinitonMapKey); }}}Copy the code

1.3)

The above code, we perform the first, no accident of system gives you a backhand “Java. Lang. StackOverflowError” exception, tell you: “brother dead cycle, dead tired I don’t you like???”

So what’s the problem? Reviewing the above code with the exception information, the problem appears in:

for (Field declaredField : declaredFields) {
    final Autowired annotation = declaredField.getAnnotation(Autowired.class);
    if(annotation! =null) {/* * indicates that the attribute is required to be injected. * Enable access permission first; Call this method recursively to get the object to be injected; * Note: In Spring you can fetch not only by name, but also by type and constructor; (byName, byType, constructor) don't worry about the details here, just simulation; * * /
        declaredField.setAccessible(true);
        // There is an infinite loop in the recursiondeclaredField.set(instance, getBean(declaredField.getName())); }}Copy the code

Because there is B in the test class A, and there is A in the test class, the recursion will continue to call; Some people say, “This is something I use a lot in development, why haven’t I encountered it?” ; That’s because Spring Ecology has solved this problem for you; Don’t worry, let’s move on;

Second, code transformation

2.1) Cache acquisition

Start by adding a method that fetches an object from the level-1 cache

* <br /> * From cache * <br /> * from cache * <br /> * from cache * <br /> * * *@author : XXSD
 * @date: 2021/3/250025 9:58 PM */
private Object getSingleton(String beanName) {
    if (LEVEL_ONE_CACHE_MAP.containsKey(beanName)) {
        /* * returns the cached object * */ if it exists
        return LEVEL_ONE_CACHE_MAP.get(beanName);
    }
    return null;
}
Copy the code

2.2) Transformation of getBean method

/** * here simulates Spring's method of obtaining beans **@author : XXSD
 * @date: 2021/3/250025 8:13 PM */
public Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
    /* * select * from cache; * If no, proceed to the next step; * * /
    final Object singleton = getSingleton(beanName);
    if(singleton ! =null) {
        return singleton;
    }

    final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
    /* * Review the life cycle of a Bean: instantiate, attribute assignment, initialize, add to level 1 cache * Step 1: simulate instantiation; Get the Class and instantiate it by reflection; * In Spring, factory methods and optional parameterized constructors are also called during Bean instantiation. This is just simulation, so don't worry about the details; * * /
    finalClass<? > beanClass = beanDefinition.getBeanClass();final Object instance = beanClass.newInstance();
    /* * move the last step directly to here, after the instantiation directly into the level cache * */
    LEVEL_ONE_CACHE_MAP.put(beanName, instance);
    
    //... Here the code is still, as before, look at the following...
    
    /* * step 3: implement the specified method or rule, such as init-mthod etc. I'm going to skip this, I don't have to deal with it; * Step 4: Put it in level 1 cache * Move this step up, not here * */
    //LEVEL_ONE_CACHE_MAP.put(beanName, instance);
    return instance;
}
Copy the code

Finally, modify the Main method

/** *@author : XXSD
 * @date : 2021/3/25 0025 下午 8:12
 */
public static void main(String[] values) throws InstantiationException, IllegalAccessException {
    final CircularDependency circularDependency = new CircularDependency();

    //... The code here can be left untouched, as before, adding the following code...

    /* * add cache after reading here directly call a try * note: here is directly use the interface to connect to the returned object, everything in the world "all interface" * */
    final ICirulationDependencyTest cirulationDependencyTestBeanA = (ICirulationDependencyTest) circularDependency.getBean("cirulationDependencyTestBeanA");
    cirulationDependencyTestBeanA.demoTest();

}
Copy the code

Run, now look at the results:

You old people is not surprised to find that there will not be a dead cycle; At this point a lot of kids will go berserk: “Level 1 caching already solves this loop dependency problem, what’s wrong with Spring? Don’t want to make out so many things, do some useless, loaded forced to the extreme !!!!”

In fact, this is not the case. Spring is an ecological environment, and the stability of the ecological environment must be considered by many factors, such as Bean creation in a multi-threaded environment, as well as the scalability level maintenance and so on. Must be clear responsibility for future expansion;

PS: Here is a long story, many people ask me to see the source code for what? My answer is: “install force” the most powerful support; Look at these excellent framework source can learn a lot of things, architecture, design, solutions, algorithms, design patterns and so on, for you have a great promotion; People are insatiable, and so are their skills. Many of the so-called distributed architecture solutions in use today are integrated into Spring in any way possible to make development easier and more efficient; If you don’t read the source code, how do you know how to package your years of experience into components that can be integrated into Spring? Doing CRUD for too long is the foundation of apes, but sitting for too long will ruin people, not always “a cup of tea, a pack of cigarettes, a Bug change the day”, and then feel good about yourself. Where is your sense of achievement? Where is he better than others?

If you look back at the code logic above, you will see that Bean — A is instantiated directly into the level 1 cache, but when the properties are processed, A recursive call to getBean is made to create Bean — B. While Bean — B is created, Bean — A is retrieved from the level 1 cache. But the BeanA is incomplete because the attributes in A have not been initialized; Does it make sense to give you a crippled Bean? Do you dare to call it outright? So, let’s continue to simulate a level 2 cache;

Third, level 2 cache

3.1) Create a level 2 cache container

/** * Attribute description: Emulated level 2 cache container * <br /> * In Spring level 1 cache container variable name is: EarlySingletonObjects * This secondary cache is used to distinguish complete beans from incomplete beans. The purpose of this cache is to clarify responsibilities and avoid reading incomplete beans * *@date : 2021/3/25 0025 下午 9:03
 */
private static final Map<String, Object> LEVEL_TWO_CACHE_MAP = new ConcurrentHashMap<>(16);
Copy the code

PS: Here’s an interview question (which is unusual, depending on the interviewer’s skills and mood) : How do I avoid loading incomplete beans in Spring? In this case, the level 2 cache is used to store incomplete beans, while the level 1 cache is used to store full beans. This is why Spring always fetches beans from the Level 1 cache. Beans retrieved from the level 1 cache are ready-to-use;

3.2) Adjustment of storage mode

Changes to getSingleton methods:

* <br /> * From cache * <br /> * from cache * <br /> * from cache * <br /> * * *@author : XXSD
 * @date: 2021/3/250025 9:58 PM */
private Object getSingleton(String beanName) {
    if (LEVEL_ONE_CACHE_MAP.containsKey(beanName)) {
        /* * returns the cached object * */ if it exists
        return LEVEL_ONE_CACHE_MAP.get(beanName);
    }else if(LEVEL_TWO_CACHE_MAP.containsKey(beanName)){
        /* * if it does not exist in level 1 cache, it will get * */ in level 2 cache
        return LEVEL_TWO_CACHE_MAP.get(beanName);
    }
    return null;
}
Copy the code

Change of the main method getBean: here, the method of getBean is restored to the form of the first step, and then directly added to the second level cache after instantiation;

/** * here simulates Spring's method of obtaining beans **@author : XXSD
 * @date: 2021/3/250025 8:13 PM */
public Object getBean(String beanName) throws IllegalAccessException, InstantiationException {
    /* * select * from cache; * If no, proceed to the next step; * * /
    final Object singleton = getSingleton(beanName);
    if(singleton ! =null) {
        return singleton;
    }

    final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
    /* * Review the life cycle of a Bean: instantiate, attribute assignment, initialize, add to level 1 cache * Step 1: simulate instantiation; Get the Class and instantiate it by reflection; * In Spring, factory methods and optional parameterized constructors are also called during Bean instantiation. This is just simulation, so don't worry about the details; * * /
    finalClass<? > beanClass = beanDefinition.getBeanClass();final Object instance = beanClass.newInstance();

    /* * put the object in level 2 cache * */
    LEVEL_TWO_CACHE_MAP.put(beanName, instance);

    /* * Step 2: Start simulating attribute assignment; First get all the attributes, through reflection to assign; * First check if @autowired * */ exists on the property
    final Field[] declaredFields = beanClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        final Autowired annotation = declaredField.getAnnotation(Autowired.class);
        if(annotation ! =null) {
            /* * indicates that the attribute is required to be injected. * Enable access permission first; Call this method recursively to get the object to be injected; * Note: In Spring you can fetch not only by name, but also by type and constructor; (byName, byType, constructor) don't worry about the details here, just simulation; * * /
            declaredField.setAccessible(true); declaredField.set(instance, getBean(declaredField.getName())); }}/* * step 3: implement the specified method or rule, such as init-mthod etc. I'm going to skip this, I don't have to deal with it; * Step 4: Place in level 1 cache * */
    LEVEL_ONE_CACHE_MAP.put(beanName, instance);
    return instance;
}
Copy the code

Execute it and see the result. Is it still successful?

Does level 2 cache solve the problem of loop dependency perfectly? In Spring the answer is yes, it is enough to solve the loop dependency on the second level cache; But why does Spring add a level 3 cache? Needless to say, is advantage loaded? Level 3 caching is necessary because of the Spring ecosystem specification, so read on;

Four, three level cache

Here level 3 cache, I really do not know how to define it, some people say for the processing of AOP dynamic proxy, some people say for the optimization of cyclic dependency, in short, there is a variety of opinions, there is no specific qualitative, you old people randomly find a reason to comfort yourself, do not tangle too much, As for the purpose of this level 3 cache, perhaps the original Spring developers can explain…….. Hahahaha!

Why say for AOP dynamic proxy? Let’s say that in development we’re intercepting with a section, using @pointcut (“execution(*… CirulationDependencyTestBeanA.*(..) ) “), create a dynamic proxy for CirulationDependencyTestBeanA, AOP agency is created in the Spring with the method of coupling process, is in the life cycle of Bean after initialization and instantiate the call immediately after the current Bean done when the post processor, If I remember correctly, Spring seems to use the JdkDynamicAopProxy class to do dynamic proxy;

When will Spring start creating AOP proxies for beans? When will Spring start creating AOP proxies for beans? Here must pay attention to, do not put the above paragraph directly without brain retelling, here needs to decorate the language ha, otherwise you will die very pit dad (why will die very pit dad, scratch your hair to think about); A more complete answer would be: First Spring create a dynamic proxy is by calling the rear of the Bean processors to achieve, do the benefits of decoupling (here must want to good, his mind trying to lead the interviewer to lead you on the way of thinking, ready to Spring ecological knowledge, the interviewer may ask these issues next); Creation time is strictly speaking two kinds, the first is after initialization call the current Bean post-processor to complete the AOP creation, the second when the cycle will call the current Bean post-processor to complete the AOP creation after instantiation; Rear Spring source code invokes the Bean processors is “AbstractAutoProxyCreator” because this object implements “SmartInstantiationAwareBeanPostProcessor” source of org. Springframewo Rk beans. Factory. Support. AbstractAutowireCapableBeanFactory# getEarlyBeanReference fragments are as follows:

/** * getEarlyBeanReference is triggered when getObject() is placed on a tertiary object. We can through the rear SmartInstantiationAwareBeanPostProcessor processor to modify the attributes of the object we early, * but spring internal SmartInstantiationAwareBeanPostProcessor getEarlyBeanReference didn't do any deal with * is in keeping with the spring open interface specification Leave a * we extension@param beanName the name of the bean (for error handling purposes)
 * @param mbd the merged bean definition for the bean
 * @param bean the raw bean instance
 * @return the object to expose as bean reference
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
   Object exposedObject = bean;
   / / interpretation in the container we have rear InstantiationAwareBeanPostProcessors types of processors
   if(! mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {// Get all of our rear processors
      for (BeanPostProcessor bp : getBeanPostProcessors()) {
         Rear / * * * this is called the processor to complete the AOP dynamic proxy * judge our post processor is implemented SmartInstantiationAwareBeanPostProcessor interface * /
         if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            // Perform a cast
            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
            / / each call SmartInstantiationAwareBeanPostProcessor getEarlyBeanReferenceexposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); }}}return exposedObject;
}
Copy the code

In Spring, there are JDK native mode and Cglib mode for proxying beans. If I remember correctly, it seems that the JDK dynamic proxy is used by default.

/ * * * *@paramConfig is used to specify our Advisor information for us * this method is used to create our proxy object * our targetClass object implements the interface, ProxyTargetClass does not specify a mandatory cglib proxy. The cglib proxy will go directly * if our ProxyTargetClass is set to false and the proxy class is an interface@return
 * @throws AopConfigException
 */
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   ProxyTargetClass =true fasle
   if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<? > targetClass = config.getTargetClass();if (targetClass == null) {
         throw new AopConfigException("TargetSource cannot determine target class: " +
               "Either an interface or a target is required for proxy creation.");
      }
      // The targetClass interface uses the JDK proxy
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      / / additional agent
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      // Dynamic proxy
      return newJdkDynamicAopProxy(config); }}Copy the code

4.1) Simulate proxy implementation

/** * Class description: The Spring proxy implementation emulates **@author XXSD
 * @version 1.0.0
 * @date 2021/3/26 0026 下午 4:54
 */
public class JdkDynamicAopProxy implements AopProxy {

    private Object bean;

    public JdkDynamicAopProxy(Object bean) {
        this.bean = bean;
    }

    /** ** this is just going through the motions, don't worry about it */
    @Override
    public Object getProxy(a) {
        return bean;
    }

    /** ** this is just going through the motions, don't worry about it */
    @Override
    public Object getProxy(ClassLoader classLoader) {
        return null; }}Copy the code

4.2) Simulate the rear processor

/** * Class description: dynamic proxy post-processor **@author XXSD
 * @version 1.0.0
 * @date 2021/3/26 0026 下午 4:07
 */
public class DynamicProxyPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
    /** * Post-processor callback method **@param bean     the raw bean instance
     * @param beanName the name of the bean
     * @return the object to expose as bean reference
     * (typically with the passed-in bean instance as default)
     * @throws BeansException in case of errors
     */
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        / * * here is simple default as CirulationDependencyTestBeanA object need agent just sample, don't care about these details * * /
        if(bean instanceof CirulationDependencyTestBeanA){
            final JdkDynamicAopProxy jdkDyncimcProxy = new JdkDynamicAopProxy(bean);
            // This returns the object of the proxy
            return jdkDyncimcProxy.getProxy();
        }
        returnbean; }}Copy the code

4.3) Dynamic proxy in the instantiation phase

According to the above, use AOP for dynamic proxy in the instantiation phase if you are in a cyclic dependency; So how do you tell if you are currently in a cyclic dependency? Here kickbacks prior knowledge “Spring using the multistage cache way to deal with, the purpose is to clear responsibility, easy to expand, easy maintenance, updated” got to apply here, we will not be a complete Bean into the second level cache, so whether can according to this condition to determine whether the current in the loop since?

PS: In the previous communication with the r&d team, there was a quick brain and immediately answered that in the getSingleton method, if the object is obtained in the level 2 cache, it is directly handled by proxy, and then the object in the level 2 cache is replaced by proxy object. This seems like a perfect answer, but THEN I ask, what if there are only two beans that depend on each other in the current example? B depends on A; C depends on A, and A depends on B and C. If you use this method, won’t this object be handled multiple times by the agent? Your product, your fine product;

4.4) Final form of sample code

Following the rules of the Spring ecosystem, avoid coupling here and add a function interface:

@FunctionalInterface
public interface FactoryAopFunction<T> {

    /** * get the Bean object * after the proxy@author : XXSD
    * @date: 2021/3/270027 12:08 PM */
    T getAopBean(a) throws Exception;
}
Copy the code

Add a level 3 cache container in the example, along with a directory container being created;

/** * Attribute description: Emulated level 3 cache container * <br /> * In Spring the variable name of the level 1 cache container is: singletonFactories * This level 3 cache is specially used to manage the callback function interface **@date : 2021/3/25 0025 下午 9:03
 */
private static final Map<String, FactoryAopFunction> LEVEL_Three_CACHE_MAP = new ConcurrentHashMap<>(16);

<br /> * In Spring this container name is: registeredSingletons; The name used to store the Bean object currently being created; * Is used to identify whether the current object is in the process of being created, and is a concept similar to entry and exit * *@date: 2021/3/270027 1:28 PM */
private static final Set<String> CREATION_CATALOG_SET = new HashSet<>();
Copy the code

Tweaks to the getSingleton method:

* <br /> * From cache * <br /> * from cache * <br /> * from cache * <br /> * * *@author : XXSD
 * @date: 2021/3/250025 9:58 PM */
private Object getSingleton(String beanName) throws Exception {
    // First fetch in level 1 cache
    final Object bean = LEVEL_ONE_CACHE_MAP.get(beanName);
    if (bean == null && CREATION_CATALOG_SET.contains(beanName)) {
        /* * see if * */ is present in level 2 cache
        if(LEVEL_TWO_CACHE_MAP.containsKey(beanName)){
            return LEVEL_TWO_CACHE_MAP.get(beanName);
        }

        /* * the bookmark container does not exist in the cache and does not exist in the bookmark container; * Create a dynamic proxy from the callback method of the three-piece cache, and then put it into the second-level cache
        final FactoryAopFunction factoryAopFunction = LEVEL_Three_CACHE_MAP.get(beanName);
        if(factoryAopFunction ! =null) {
            final Object aopBean = factoryAopFunction.getAopBean();
            // Put it in level 2 cache
            LEVEL_TWO_CACHE_MAP.put(beanName, aopBean);
            // Remove the contents of the level 3 cache, which have already been processed
            LEVEL_Three_CACHE_MAP.remove(beanName);
            returnaopBean; }}return bean;
}
Copy the code

Adjustments to the core method:

/** * here simulates Spring's method of obtaining beans **@author : XXSD
 * @date: 2021/3/250025 8:13 PM */
public Object getBean(String beanName) throws Exception {
    /* * select * from cache; * If no, proceed to the next step; * * /
    final Object singleton = getSingleton(beanName);
    if(singleton ! =null) {
        return singleton;
    }

    /* * You need to start creating the Bean * bookmark the Bean name * */
    if(! CREATION_CATALOG_SET.contains(beanName)) { CREATION_CATALOG_SET.add(beanName); }final RootBeanDefinition beanDefinition = (RootBeanDefinition) BEAN_DEFINITION_MAP.get(beanName);
    /* * Review the life cycle of a Bean: instantiate, attribute assignment, initialize, add to level 1 cache * Step 1: simulate instantiation; Get the Class and instantiate it by reflection; * In Spring, factory methods and optional parameterized constructors are also called during Bean instantiation. This is just simulation, so don't worry about the details; * * /
    finalClass<? > beanClass = beanDefinition.getBeanClass();final Object[] instance = new Object[]{beanClass.newInstance()};

    /* * Here we put the callback into the interface of the callback into the level 3 cache, * combined with the composite example: Spring calls the post-processor to create the proxy object when instantiated and in a loop dependency and after instantiation; * In any node can be quickly called, but also in the function implementation to expand more processing, giving enough developers can expand the opportunity, easy to maintain, expand and use * */
    LEVEL_Three_CACHE_MAP.put(beanName, () -> new DynamicProxyPostProcessor()
            .getEarlyBeanReference(instance[0], beanName));

    /* * Step 2: Start simulating attribute assignment; First get all the attributes, through reflection to assign; * First check if @autowired * */ exists on the property
    final Field[] declaredFields = beanClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        final Autowired annotation = declaredField.getAnnotation(Autowired.class);
        if(annotation ! =null) {
            /* * indicates that the attribute is required to be injected. * Enable access permission first; Call this method recursively to get the object to be injected; * Note: In Spring you can fetch not only by name, but also by type and constructor; (byName, byType, constructor) don't worry about the details here, just simulation; * * /
            declaredField.setAccessible(true);
            declaredField.set(instance[0], getBean(declaredField.getName())); }}/* * step 3: implement the specified method or rule, such as init-mthod etc. I'm going to skip this, I don't have to deal with it; * Step 4: Place in level 1 cache * */
    if(LEVEL_TWO_CACHE_MAP.containsKey(beanName)){
        instance[0] = LEVEL_TWO_CACHE_MAP.get(beanName);
    }

    LEVEL_ONE_CACHE_MAP.put(beanName, instance[0]);
    return instance[0];
}
Copy the code

Execute it and see how it works;

PS: As for the AOP related knowledge, in the back to share with you, here is not the first chat;

Five, the summary

5.1) How does Spring solve the problem of loop dependency

In Spring, multi-level (three-level) caching is used to handle and resolve cycle dependencies; Each level acts on different types, which is conducive to clear division of responsibility, easy to expand, easy to maintain;

Note: “Level 1 cache is called singleton pool again”; Note: If the interviewer is going to ask you, it’s a good idea to include this sentence as a preparation for later interview questions, because the interviewer will probably ask you, “How does Spring solve cyclic dependencies in multiple cases?” It is not known how many people died in the pit;

5.2) Why use level 2 cache and Level 3 cache

In Fact, the use of level 1 caching in Spring is sufficient to handle the problem of loop dependencies, you can use synchronization, you can use locking, etc. Only the second level cache can also solve the circular dependency, but as an ecosystem, Spring must consider expansion and maintenance, and the premise of expansion and maintenance is a clear division of labor. So there is a three-level cache, which is mainly used to store the function callback interface object. The post-processor of the Bean is called in the function method to complete the implementation of the AOP proxy, mainly because of decoupling, so that the logic of calling the post-processing can be separated for future maintenance. At the same time, it also provides a good expansion support for the developers themselves;

Level 2 cache: used to store incomplete beans. The responsibility is to maintain the incomplete beans and to distinguish the complete beans from the incomplete beans. This can also solve the problem of loop dependency, but not as extensible as it should be, for the following reasons: fetching the tertiary cache —–getEarlyBeanReference() goes through a series of post-processing to specialization our earlier objects; When a wrapper object is fetched from the tertiary cache, it goes through a post-processor to specialize the bean of our earlier object, but Spring’s native post-processor is left unprocessed and left for us programmers to extend;

PS: If I ask you what level 3 cache is for, you are free according to your own understanding (I will answer storing proxy objects and related knowledge), because there is really no exact definition, at least I did not find this food force;

5.3) What objects are stored in the level 2 cache

Instead of the original instance object, the object after the AOP proxy is stored in the level 2 cache. The reason: In A and B under the condition of circular dependencies, began after to instantiate A attribute assignment, at the time of attribute assignment came to B, B is actually A through AOP proxy objects, recursion, A when you return to continue to implement A need in the second level cache access it again, if get or the original instance at this moment, the original is not complete it is incomplete, There is no end;

5.4) How does Spring address the circular dependencies of constructors

No, because the constructor is called in the instantiation phase of the Bean declaration cycle, there is no way to handle it here. ;

PS: But here’s the thing: do you think you can successfully initialize a single constructor if it has cyclic dependencies? Would it be more efficient to use constructors instead of @autowired to plan class structure logic if development was not necessary?

5.5) How does Spring solve cyclic dependencies in multiple cases

First of all, we should make clear the difference between multiple cases and single cases. Multiple examples: an object has multiple instantiated objects; Singleton: globally unique; So many instances do not exist in the cache object (level 1 cache container), level 1 cache is called the singleton pool, will only store singleton objects; Without the support of any cache, loop dependencies cannot be solved, so Spring will throw an exception if it encounters multiple cases of loop dependencies