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

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

  • 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;
}

Class B:

public class B {
    private A a;
}

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:

Private static Map 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 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 beanClass) {// This article simply replaces the bean naming rule String beanName = with the Class name beanClass.getSimpleName().toLowerCase(); // If it is already a bean, return if (cachemap.containsKey (beanName)) {return (T) cachemap.get (beanName); } / / the Object itself is instantiated Object Object. = beanClass getDeclaredConstructor (). The newInstance (); Cachemap. put(beanName, object); Field[] fields = object.getClass().getDeclaredFields(); Field[] fields = object.getClass(). for (Field field : fields) { field.setAccessible(true); FieldClass = field.getType(); String fieldBeanName = fieldClass.getSimpleName().toLowerCase(); Field. Set (Object, cachemap.containsKey (fieldBeanName)? cacheMap.get(fieldBeanName) : getBean(fieldClass)); } // Return (T) object; }

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 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"); }} // Author: LeetCode // Link: https://leetcode-cn.com/problems/two-sum/solution/liang-shu-zhi-he-by-leetcode-2/ // Source: Force link (LeetCode)

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.