This is the 14th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

One, foreword

How Spring resolves circular dependencies is a popular Java interview question in the last two years.

In fact, I have a certain skepticism about this kind of framework source code.

If I were an interviewer, I might ask scenarios like “If the injected property is null, which direction would you go?”

Now that I’ve written this article, without further ado, I’ll take a look at how Spring solves circular dependencies and what they really are.

Second, the body

In general, if you ask Spring how to deal with loop dependencies internally, it’s a single default singleton Bean that references properties to each other.

Such as cross-references between several beans:

Or even “cycle” themselves:Prototype scenarios do not support loop dependencies, and AbstractBeanFactory classes usually go to the following judgment and throw an exception. = =

if (isPrototypeCurrentlyInCreation(beanName)) {
  throw new BeanCurrentlyInCreationException(beanName);
}
Copy the code

Is it StackOverflow or OutOfMemory?

Spring fear you not guess, just throw the BeanCurrentlyInCreationException first

Constructor based loop dependencies, not to mention the official documentation is on the table, you want constructor injection to support loop dependencies, there is no such thing as a code change.

So for the default singleton property injection scenario, how does Spring support circular dependencies?

Spring addresses loop dependencies

First, Spring maintains three maps internally, which is commonly referred to as a level 3 cache. The author looked through the Spring documentation but did not find the concept of three-level cache, which is probably a local term for ease of understanding. In the Spring of DefaultSingletonBeanRegistry class, you will suddenly found that the three Map was hanging over class:

  1. SingletonObjects is our most familiar friend, commonly known as “singleton pool” or “container”, the cache where the singleton Bean is created and completed.
  2. The singletonFactories map creates the original factory of the Bean
  3. EarlySingletonObjects is an early reference to a mapping Bean, meaning that the Bean in the Map is not complete, or even called a “Bean”, just an Instance.

The last two maps are actually‘Stepping Stones’Level, is only used to create a Bean, the creation of a clear. So the author is right“Level 3 Cache”This is a bit confusing, probably because comments start with Cache of. == why become the last two maps as stepping stones, assuming the final placeSingletonObjects BeanIt’s that cold cup you want. So Spring has two cups, namelysingletonFactoriesandearlySingletonObjectsPour back and forth several times to dry the hot water“Cool white open”In thesingletonObjectsIn the. Without further ado, it is all condensed in the picture.

This is a GIF, and if you haven’t seen it, it probably hasn’t loaded yet. One frame in three seconds, not your card. I have drawn 17 diagrams to simplify the main steps of Spring, with the aforementioned level 3 caching at the top of the GIF and the main methods shown below. Of course, at this point you must be combined with the Spring source code to see, or certainly not understand. If you’re just looking for an overview, or an interview, keep in mind what I mentioned above as “three levels of caching” and the essence of what’s coming next.

The nature of circular dependencies

Now that we’ve seen how Spring handles circular dependencies, let’s step out of the “read source code” mindset and imagine you were asked to implement a feature that had the following characteristics. What would you do?

  • Takes some of the specified class instances as singletons
  • The fields in the class are also instantiated as singletons
  • Support for circular dependencies

For example, suppose there is class A:

public class A {
    private B b;
}
Copy the code

Class B:

public class B {
    private A a;
}
Copy the code

Let’s make it look like Spring: Pretend that A and B are decorated with @Component, and that the fields in the class are decorated with @Autowired, and then put them in the Map. In fact, very simple, the author wrote a rough code, for reference:

     /** * place the created bean Map */
    private static Map<String, Object> cacheMap = new HashMap<>(2);
 
    public static void main(String[] args) {
        // pretend to scan the object
        Class[] classes = {A.class, B.class};
        // Pretend the project initializes the instantiation of all beans
        for (Class aClass : classes) {
            getBean(aClass);
        }
        // check
        System.out.println(getBean(B.class).getA() == getBean(A.class));
        System.out.println(getBean(A.class).getB() == getBean(B.class));
    }
 
    @SneakyThrows
    private static <T> T getBean(Class<T> beanClass) {
        // This article simply replaces the bean naming convention with lowercase class names
        String beanName = beanClass.getSimpleName().toLowerCase();
        // If it is already a bean, return it directly
        if (cacheMap.containsKey(beanName)) {
            return (T) cacheMap.get(beanName);
        }
        // Instantiate the object itself
        Object object = beanClass.getDeclaredConstructor().newInstance();
        // Put it in the cache
        cacheMap.put(beanName, object);
        // Treat all fields as beans to be injected, create and inject into the current bean
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            // Get the class of fields to injectClass<? > fieldClass = field.getType(); String fieldBeanName = fieldClass.getSimpleName().toLowerCase();// If the bean to be injected is already in the cache Map, inject the value from the cache Map into this field
            // If the cache is not created
            field.set(object, cacheMap.containsKey(fieldBeanName)
                    ? cacheMap.get(fieldBeanName) : getBean(fieldClass));
        }
        // Property is completed, return
        return (T) object;
    }
Copy the code

What this code does is it handles the loop dependency, and when it’s done, cacheMap has the entire “Bean” in it

This is the essence of “circular dependencies”, not “how Spring solves circular dependencies”. The reason for using this example is to find that a small number of friends get stuck in “reading source code” and forget the essence of the problem. To see the source code and see the source code, the result has been unable to understand, but forget what the essence is. If you really don’t understand it, it would be better to write the basic version first and reverse why Spring is implemented this way. What? The essence of the question is two sum! Does the code I just wrote look familiar? That’s right. It’s similar to two sum. Two sum is the number 1 question in leetcode, which is the first question for most people to get started with algorithms. Often mocked by people, algorithmic company, authorized by the interviewer, close to. Let’s go through the motions with two sum. The question is: Given an array, given a number. Returns two indexes in an array that can be added to get the specified number. For example, given nums = [2, 7, 11, 15], target = 9 return [0, 1], because 2 + 7 = 9

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                return new int[] { map.get(complement), i };
            }
            map.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two sum solution"); }}Copy the code

The current number is saved in the Map. If the required number is found, it is returned together.

Is it the same as the code above?

If there is no Bean in the cache, instantiate the current Bean and put it into the Map. If there is any Bean that needs to depend on the current Bean, it can be fetched from the Map.

Third, the end

If you’re one of those people I mentioned above who “got stuck reading source code,” this should help you.

Some friends may wonder why Spring is so complicated to deal with a “two-sum”. Just think about how many features Spring supports. Various injection methods.. Various Bean loading, verification.. Various callback, AOP processing, etc..

Spring is more than dependency injection, and Java is more than Spring. If we’re stuck in a corner, it might be better to jump out and see.

4. Reference connection

  1. Diagram Spring solves loop dependencies
  2. Soul painting hand diagram Spring cycle dependency
  3. How does Spring solve the problem of loop dependencies
  4. How does Spring solve the problem of loop dependencies
  5. How does Spring address loop dependencies