preface

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.

The body of the

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

When you create A new field A, you find that you want to inject the prototype field B. When you create A new field B, you find that you want to inject the prototype field A.

Is it StackOverflow or OutOfMemory?

Spring is afraid that you will not guess, so he throws firstBeanCurrentlyInCreationException

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:

  • SingletonObjects is our most familiar friend, commonly known as “singleton pool” or “container”, the cache where the singleton Bean is created and completed.

  • The singletonFactories map creates the original factory of the Bean

  • 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-stone” level maps that are used for Bean creation and then removed.

That’s why I was confused by the term “level 3 Cache” in my previous post, probably because comments start with Cache of.

Why become the last two maps as stepping stones, assuming that the Bean that ends up in singletonObjects is the cup of “cool white” you want.

So Spring prepared two singletonFactories and earlySingletonObjects, and put the hot water into singletonObjects.

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> 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 inject

Class<? > 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 will have the entire”Bean

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.

I don’t know what two sum is.

Two sum is the number 1 problem in LeetCode, which is the first problem 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] and target = 9, return [0, 1] because 2 + 7 = 9

The optimal solution to this problem is to iterate once +HashMap:

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");

    }

}

// By LeetCode

// Link: https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-2/

// Source: LeetCode

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.

At 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.