4
头图

Author: Xiao Fu Ge
Blog: https://bugstack.cn

Precipitate, share, and grow, so that you and others can gain something! 😄

I. Introduction

What can delayed gratification bring to you?

University has four years, but almost everyone finds it hard to find a good job until graduation, especially the software development industry that I can be very familiar with. Even after graduation, I still need to spend extra money in training institutions to learn programming. Skills can go out to find a job. seems to have not learned anything in the past few years at school!

Personally, it may be because I like programming during school, I also heard from my brothers and sisters about the difficulty of finding a job after graduation, and I also learned about the level of programmer development skills required in the society. That is to say, after receiving these news, plus my willingness to toss, I set myself a small goal that I can accomplish every day:

红尘世界几个王,我自不服迎头上。
日敲代码两百行,冲进世界五百强。

Hahaha , just like that, 200 lines of code per day, 6,000 lines per month, 60,000 lines per year, and 180,000 lines of internship after three years. A fresh intern has nearly 200,000 lines of code. I can almost complete all kinds of simple tasks with great proficiency. After adding the real of the entire project process during the internship, it is still very easy to find a serious development work of 16093c531ef8b7.

The ease of finding a job at this time comes from your continuous study and precipitation, but if you do not go through these efforts, you may become very flustered after graduation, and in the end there is no way to go to some institutions to study again.

2. Interview questions

Thanks for the plane, a small note! , I used to feel that Spring was nothing, I read a getBean, my goodness!

Xie aircraft : Interviewer, I recently read Spring's getBean and found that there are many things here, and there is another one to solve the circular dependency. Is there anything to ask about this thing in the interview?

interviewer : Wow, how does Spring solve circular dependencies?

Xie aircraft : Well, it is solved by exposing objects in advance through the three-level cache.

Interviewer : Yes, what kind of object information is stored in these three caches?

Xie aircraft : The first level cache stores complete objects, also called finished objects. The second-level cache stores semi-finished objects, which are objects whose attributes have not yet been assigned. The third-level cache stores ObjectFactory<?> , which is used to deal with AOP circular dependencies.

Interviewer : Yes, thank you for your preparation! So if there is no three-level cache, only two or one level, can it solve the circular dependency?

Xie aircraft : Actually I have read the information and it can be solved, but Spring has to guarantee several things. Only the first level cache processing flow cannot be split, and the complexity will increase. At the same time, semi-finished objects may have null pointer exceptions. . Separating the semi-finished product from the finished product makes the processing more elegant, simple, and easy to expand. In addition, Spring’s two major features include not only IOC but also AOP, which is the method based on bytecode enhancement. Where should it be stored? The three-level cache is the most important. The circular dependency to be solved is the processing of AOP, but if Advance the creation of AOP proxy objects, and the second-level cache can also be solved. However, this violates Spring's principle of creating objects. Spring prefers to initialize all ordinary Beans and handle the initialization of proxy objects.

Interviewer : Airplane, not bad, I learned a lot this time. Then ask a simple question, have you used the solution of circular dependency?

Thanks to the plane : Oh, no, I haven't practiced it! ! ! You should really do it, try it.

3. What is circular dependency?

1. Problem description

Understanding the nature of the problem and then analyzing the problem is often more conducive to a deeper understanding and research of the problem. So before we analyze Spring's source code for circular dependencies, we must first understand what circular dependencies are.

  • There are three types of cyclic dependencies: self-dependence, mutual cyclic dependency, and multiple groups of cyclic dependencies.
  • But regardless of the number of circular dependencies, the essence of circular dependencies is the same. That is, your complete creation depends on me, and my complete creation also depends on you, but we can't decouple each other, which ultimately leads to the failure of dependency creation.
  • So Spring provides a setter cycle dependency injection solution in addition to constructor injection and prototype injection. Then we can also try this kind of dependency first, and how to solve it if we handle it ourselves.

2. Problem manifestation

public class ABTest {

    public static void main(String[] args) {
        new ClazzA();
    }

}

class ClazzA {

    private ClazzB b = new ClazzB();

}

class ClazzB {

    private ClazzA a = new ClazzA();

}
  • This code is the original appearance of the circular dependency. You have me in you, and I have you in me. If you run it, it will report error java.lang.StackOverflowError
  • This kind of cyclic dependency code cannot be solved. When you see the get/set or annotation provided in Spring, the reason why it can be solved is first of all decoupling. Separate the creation of classes from the filling of attributes, first create a semi-finished Bean, and then process the filling of attributes to complete the provision of finished Beans.

3. Problem handling

There is a core purpose in this part of the code. Let's solve the circular dependency ourselves. The plan is as follows:

public class CircleTest {

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

    public static void main(String[] args) throws Exception {
        System.out.println(getBean(B.class).getA());
        System.out.println(getBean(A.class).getB());
    }

    private static <T> T getBean(Class<T> beanClass) throws Exception {
        String beanName = beanClass.getSimpleName().toLowerCase();
        if (singletonObjects.containsKey(beanName)) {
            return (T) singletonObjects.get(beanName);
        }
        // 实例化对象入缓存
        Object obj = beanClass.newInstance();
        singletonObjects.put(beanName, obj);
        // 属性填充补全对象
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Class<?> fieldClass = field.getType();
            String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
            field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.get(fieldBeanName) : getBean(fieldClass));
            field.setAccessible(false);
        }
        return (T) obj;
    }

}

class A {

    private B b;

    // ...get/set
}

class B {
    private A a;

        // ...get/set
}
  • This code provides two classes, A and B, which are dependent on each other. But the dependencies in the two classes are filled in the way of setters. That is, the only way to avoid the two classes having to rely on the other object at the beginning of creation.
  • getBean is the core content to solve the cyclic dependency. After A is created, it depends on B when filling the attributes, then create B. When creating B and starting to fill, it is found to depend on A, but at this time the semi-finished object A has been stored in the cache to singletonObjects In, so B can be created normally, and A is also created completely through recursion.

Fourth, source code analysis

1. Talk about the details

Through the above example, we probably know that when A and B depend on each other, after A is created, attribute B is filled, and B is continued to be created, and then attribute A can be obtained from the cache, as follows:

So what does it look like to solve the circular dependency of things in Spring? Expand the details!

Although , the core principle of solving circular dependencies is the same, but it will become more complicated when supporting the IOC and AOP features of the entire Spring. The entire process of processing Spring circular dependencies is as follows;

  • The above is about the acquisition process of a circularly dependent object in Spring, which is the you want to talk about the details
  • At first glance, there are many processes, but these are basically the code fragments that you must go through when debugging the code. It is very convenient to get this execution process and debug again.

2. Processing

The source code analysis of the case involved in this chapter has been updated to github: https://github.com/fuzhengwei/interview -interview-31

The following is the get Bean operation that depends on AB in the unit test, the focus is to follow up the source code of getBean;

@Test
public void test_alias() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    Bean_A bean_a = beanFactory.getBean("bean_a", Bean_A.class);
    logger.info("获取 Bean 通过别名:{}", bean_a.getBean_b());
}

org.springframework.beans.factory.support.AbstractBeanFactory.java

@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    return doGetBean(name, requiredType, null, false);
}
  • After entering from getBean, the operation of obtaining bean will enter doGetBean.
  • The reason for this packaging is that doGetBean has many overloaded methods for different input parameters, which is convenient for external operations.

doGetBean method

protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {
    
  // 从缓存中获取 bean 实例
    Object sharedInstance = getSingleton(beanName);
    
            // mbd.isSingleton() 用于判断 bean 是否是单例模式
            if (mbd.isSingleton()) {
              // 获取 bean 实例
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    @Override
                    public Object getObject() throws BeansException {
                        try {
                          // 创建 bean 实例,createBean 返回的 bean 实例化好的
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            destroySingleton(beanName);
                            throw ex;
                        }
                    }
                });
                // 后续的处理操作
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
            
    // ...

  // 返回 bean 实例
    return (T) bean;
}
  • According to the flow chart of the source code analysis, this part is to first judge whether there is an instance object from getSingleton. For the first entry, there is definitely no object, so continue to go down.
  • After judging the mbd.isSingleton() singleton, I started to use the ObjectFactory-based packaging method to create createBean. After entering, the core logic is to start the doCreateBean operation.

doCreateBean method

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
        throws BeanCreationException {
    
      // 创建 bean 实例,并将 bean 实例包装到 BeanWrapper 对象中返回
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    
        // 添加 bean 工厂对象到 singletonFactories 缓存中
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
              // 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 bean。
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
        
    try {
      // 填充属性,解析依赖关系
        populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    }
    
    // 返回 bean 实例
    return exposedObject;
}
  • There are many things included in the doCreateBean method, but the core is mainly to create an instance, join the cache, and finally fill in the attributes. The attribute filling is to fill in the classes involved in each attribute field of a bean.
  • createBeanInstance , create a bean instance, and wrap the bean instance in a BeanWrapper object to return
  • addSingletonFactory , add the bean factory object to the singletonFactories cache
  • getEarlyBeanReference , get the early reference of the original object, in the getEarlyBeanReference method, AOP related logic will be executed. If the bean is not intercepted by AOP, getEarlyBeanReference returns the bean as it is.
  • populateBean , fill in attributes, resolve dependencies. That is, from here to find the attribute B in the A instance, then create the B instance, and finally return.

getSingleton L3 cache

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 从 singletonObjects 获取实例,singletonObjects 是成品 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 判断 beanName ,isSingletonCurrentlyInCreation 对应的 bean 是否正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
          // 从 earlySingletonObjects 中获取提前曝光未成品的 bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
              // 获取相应的 bean 工厂
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                  // 提前曝光 bean 实例,主要用于解决AOP循环依赖
                    singletonObject = singletonFactory.getObject();
                    
                    // 将 singletonObject 放入缓存中,并将 singletonFactory 从缓存中移除
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
  • singletonObjects.get(beanName) , get the instance from singletonObjects, singletonObjects is the finished bean
  • isSingletonCurrentlyInCreation , to determine whether the beanName, isSingletonCurrentlyInCreation corresponding to the bean is being created
  • allowEarlyReference , get the unfinished bean exposed in advance from earlySingletonObjects
  • singletonFactory.getObject() , expose bean instances in advance, mainly used to solve AOP circular dependencies

sum up , it is a code flow that deals with circular dependencies. The content extracted in this part is mainly the core content, and it is not disassembled from all the long talks. You will involve more when debugging, and try to do your own Operate and debug several times according to the flowchart.

3. Dependency Resolution

In summary, we have tried to solve the circular dependency by ourselves, and learned the core solution principle of circular dependency. It also analyzes the processing process of the circular dependency solved by Spring and the analysis of the core source code. So next, we will summarize the different processing processes of the lower three levels of cache, which is a summary and is also convenient for everyone to understand.

1. Can the first level cache be solved?

  • In fact, only the first level cache is not incapable of solving circular dependencies, just like our own example.
  • But in Spring, if it is handled like our example, it will become very troublesome, and NPE problems may also occur.
  • Therefore, in accordance with the code processing process in Spring as shown in the figure, we analyze the process of storing finished Beans such as the first-level cache, which cannot solve the problem of circular dependency. Because the creation of A's finished product depends on B, and the creation of B's finished product depends on A, when the attributes of B need to be completed, A is still not created, so an infinite loop will occur.

2. Can the secondary cache be solved?

  • With the second-level cache, this matter is actually easy to handle. One cache is used to store finished objects, and the other is used to store semi-finished objects.
  • After creating the semi-finished object, A is stored in the cache, and then the attributes of the A object that depend on B are added.
  • B continues to create, and the created semi-finished product is also placed in the cache. When the A attribute of the object is supplemented, it can be obtained from the semi-finished product cache. Now B is a complete object, and then A is also a complete object like a recursive operation .

3. What does the third-level cache solve?

  • With the second-level cache, Spring's dependencies can be resolved, so how come there is a third-level cache. In fact, we mentioned in the previous analysis of the source code that the three-level cache is mainly to solve the characteristics of Spring AOP. AOP itself is an enhancement to the method. It is ObjectFactory<?> , and Spring's principle does not want to pre-create this type of Bean, so it must be stored in the third-level cache for processing.
  • In fact, the overall process is similar, except that when B fills attribute A, it first queries the finished product cache, then checks the semi-finished product cache, and finally sees if there is a singleton project in the third-level cache. In the end, it will call the getObject method to return the proxy reference or the original reference.
  • So far, the three-level cache problem brought by Spring AOP has been solved. The AOP involved in this chapter relies on source code examples and can be debugged

Five, summary

  • Recall that this article basically started with practical examples to guide everyone to have an overall understanding of circular dependencies, and also examples of its solutions that can be used, so that the follow-up Spring's solution to circular dependencies will not be so unfamiliar. .
  • Throughout the article, you can also see that the three-level cache is not mandatory, but it is necessary to meet the principles of Spring's own creation. If you can download the Spring source code to make changes to this part of the code, create an AOP object in advance and save it in the cache, then the second-level cache can also solve the circular dependency problem.
  • Regarding circular dependencies, it may not be a good coding method. If you still want to use more reasonable design patterns to avoid circular dependencies in your own programs, these methods may increase the amount of code, but it will be more convenient to maintain. Of course this is not mandatory, it can be based on your needs.

Sixth, series recommendation


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。