Written in the beginning

In the process of learning Spring, the Spring cycle depends on roughly understand, but they carefully track the source code, but always almost mean, vaguely understand is very agitated, and then hard work, must figure it out, liver under this article, also hope to see this article friends can harvest.

☘ Spring loop dependencies

🤔 What are circular dependencies?

For example, 🌰

/** * class A, introducing the attribute B */ of class B
public class A {
  private B b;
}
Copy the code
/** * class B, introducing the attribute A */ of class A
public class B {
  private A a;
}
Copy the code

Here’s another simple picture:

In this case, a relies on B to create a, which in turn relies on A to create B, which in turn relies on A to create B, which in turn relies on A to create B……

🙂 interdependent when, dead cycle? 👉 noh, this is cyclic dependency!


Cyclic dependencies are not a problem or a bug, and we might actually use them in development. Take A and B at the beginning, we will use the following method when manually using them:

  A a = new A();
  B b = new B();
  b.setA(a);
  a.setB(b);
Copy the code

In fact, this solves the loop dependency, which is functionally fine, but why does Spring solve the loop dependency?

🤔 Why does Spring address loop dependencies?

First, let’s take a quick look at what the Spring framework does for us. To sum up, six words: IoC and AOP.

Since Spring addresses cyclic dependencies with IoC and AOP in mind, I’ll mention them here.

Since the core of this article is Spring’s cyclic dependency processing, IoC and AOP will not be covered in detail. If you want to know more about it, please refer to 😶 later

IoC mainly leaves the creation and management of objects to Spring, which can solve the coupling problem between objects and save time and effort for developers.

AOP, mainly in the case of not changing the original business logic, enhance the cross-cutting logic code, but also decouple, avoid cross-cutting logic code repetition; OOP is also a continuation, complement.

Since the instantiation of a class is left to Spring to manage, the loop dependency on Spring must also take into account how it is handled (how it always feels like crap 😶).

🤯 ways to resolve circular dependencies

Spring does this by creating new objects and then setting property values. Unfortunately 🙃, why later), the underlying instantiation of Spring-managed beans is actually implemented by reflection.

There are several ways we can instantiate, such as assigning a property once through a constructor, like the following

// Suppose there is a student class
public class Student {
  private int id;
  private String name;

  public Student(int id, String name) {
    this.id = id;
    this.name = name; }}// Instantiate and assign by constructor
new Student(1."Suremotoo");

Copy the code

But you can’t solve loop dependencies with constructors! Why not?

A and B depend on each other, and instantiate A through the constructor as follows

new A(b);
Copy the code

😮 Wow, did you see the problem? To use the constructor method, first instantiate the property value. If A is dependent on b, it needs to be instantiated by b first. If b is instantiated by A, it needs to be instantiated by A❗️.

We can use set to solve this problem, but what about Spring using set? The answer is yes.

Since the bottom layer is implemented by reflection, and we use reflection ourselves, the general idea is this (again, using A and B as examples).

  1. Instantiate class A first

  2. Instantiate class B

  3. Set a property in class B

  4. Set A b attribute in class A

In essence, reflection implements the following code

      A a = new A();
      B b = new B();
      b.setA(a);
      a.setB(b);
Copy the code

And here’s a little bit of explanation for why that works.

A A = new A(), indicating that A is only instantiated, not initialized

Similarly, B B = new B(), it’s just instantiated, it’s not initialized

a.setB(b); , assign the attribute of A to complete the initialization of A

b.setA(a); , assign the attribute of B to complete the initialization of B

Have a bit feeling now, cheat the dog to come in first, kill 😬 again

How does Spring solve the problem of loop dependencies

Let’s start with the popular answer, level 3 caching.

	/** * Cache of singleton objects: bean names -- bean instances, i.e., so-called singleton pools. * Represents a Bean object that has gone through a full life cycle *  first level cache  */
	Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** * Caching of early singleton objects: bean name -- bean instance. * indicates that the Bean has not completed its life cycle (the Bean's properties have not been filled) and is stored in this cache * that is, beans that are instantiated but not initialized are placed in this cache *  Second level cache  */
	Map<String, Object> earlySingletonObjects = new HashMap<>(16);
	
	/** * Caches for singleton factories: bean name -- ObjectFactory. * represents the factory where the generated beans are stored *  third level cache  */Map<String, ObjectFactory<? >> singletonFactories =new HashMap<>(16);
Copy the code

Comments in the code may not be clear, I will paste a third level cache:

Level 1 cache (also called singleton pool) : Map

singletonObjects, which holds Bean objects that have gone through a full life cycle
,>

Level 2 cache: Map

earlySingletonObjects, which holds the Bean objects that were exposed earlier than the end of the Bean’s life cycle (the properties have not been filled)
,>

Map

> singletonFactories, which stores factories that can generate beans
,>

Spring-managed beans are singletons by default, which means that Spring puts the final beans that can be used into the first level cache, singletonObjects, from which all future Bean applications can be retrieved.

Is it ok to just use level 1 cache?

Since they are all retrieved from singletonObjects, is it ok to use only one singletonObjects*? Certainly not. First, singletonObjects stores fully initialized beans that can be used directly. If we put the uninitialized Bean directly into singletonObjects, it is very likely that the uninitialized Bean will be used by another class.

A, B, C 🌰

  1. So let’s instantiate class A, let’s call it A
  2. Add A to singletonObjects (the b property of A is still empty)
  3. Class C needs to use class A, go to singletonObjects to get, and get A
  4. Class C uses a, takes the B attribute of class A, and then NPE.

Noh, something is wrong, this is not solving the problem of cyclic dependency, but the wrong design.

NPE 就是 NullPointerException

Is it ok to use level 2 cache?

A review of cyclic dependencies: A→B→A→B……

It’s all about breaking the loop, level 1 cache doesn’t work, so let’s just add another level, ok? Let’s look at a picture

The cache in the figure is a level 2 cache

After looking at the picture, you may also wonder, A did not complete initialization into the cache, then B is not using incomplete A, yes! In the whole process, there is only one A, and the A in B is only A reference to A, so after A completes the initialization, the A in B is also completed naturally. Here is the manual setA, setB mentioned earlier in the article, where I posted the code:

  A a = new A();
  B b = new B();
  b.setA(a); // set b's property a, which is actually a reference to A
  a.setB(b); // If a is initialized, b is initialized. // If a is initialized, b is initialized
Copy the code

At this point, we can see that level 2 cache solves the problem of loop dependency, but why level 3 cache? This is the life cycle of beans in Spring.

Management of beans in Spring

To understand cyclic dependencies in Spring, you need to first understand the life cycle of beans in Spring.

Spring-managed objects are called beans. The lifecycle of a Bean will not be described in detail, but the general process will be described to help you understand cycle dependencies.

The life cycle of a Bean in Spring refers to the sequence of life activities of a Bean from creation to destruction.

The main steps for Spring to manage beans are:

  1. Spring scans which classes are managed by Spring, depending on the developer’s configuration, and generates a BeanDefintion for each class, which encapsulates information about the class, such as the fully qualified class name, which attributes, whether a singleton, and so on

  2. De-instantiated beans (in this case, instantiated but uninitialized beans) via reflection based on BeanDefintion information

  3. Populate the properties in the uninitialized object above (dependency injection)

  4. If the method in the above uninitialized object is AOP, then you need to generate proxy classes (also known as wrapper classes)

  5. Finally, the initialized object is stored in the cache (called “singletonObjects” in Spring cache), and the next time you use the cache to get OK

If AOP is not involved, the proxy class is not generated in step 4, and the object that completes the property population in step 3 is cached.

What’s wrong with level 2 caching?

If beans don’t have AOP, there’s really no problem with using second-level caching, which is a problem once you have step 4 of the life cycle above. Because AOP processing, it is often necessary to generate proxy objects, proxy objects and the original object is not the same object at all.

In the second-level caching scenario, assuming that A method of class A will be AOP, the process looks like this:

  1. Generate an instance of A and put it in cache. A needs B
  2. Regenerating into B, when filling B, need A, get a from the cache, complete the initialization of B;
  3. Then A takes the initialized B and uses it to complete the property filling and initialization of A
  4. Since class A involves AOP, and then CLASS A generates A proxy class, let’s call it proxy A

The result: the final product of A is agent A, which should also be used in B, but now B uses the original A

Agent A and original A are not the same object at all, and now this is a problem.

How to solve using level 3 cache?

Map

singletonFactories > < singletonFactories > < singletonFactories > < singletonFactories >
,>

SingletonFactories stores a beanName and its corresponding ObjectFactory, which is essentially the factory that generated the Bean. In practice, the ObjectFactory is a Lambda expression :() -> getEarlyBeanReference(beanName, MBD, bean), and this expression is not executed.

So what exactly does getEarlyBeanReference do?

The core is two steps:

earlyProxyReferences = new ConcurrentHashMap<>(16);,>

Step 2: Generate the Bean’s corresponding proxy class return

This earlyProxyReferences is simply used to keep track of which beans have been AOP executed to prevent beans from being AOP again later

So when is the getEarlyBeanReference triggered and executed?

In the example of the second-level cache, A is needed to populate the properties of B, and then A is fetched from the third-level cache. If it exists, the getEarlyBeanReference function is executed, which returns the proxy object corresponding to A.

The proxy Object is then placed in the second level cache, Java Map

earlySingletonObjects.
,>

Why not put it in level 1 cache?

The proxy object that you get at this point is also unpopulated, that is, an object that is still uninitialized.

If you put it directly into the first level cache, then it is used by other classes, there is definitely a problem.

So when do I put it in level 1 cache?

Here we need to briefly say the role of the second level cache, if A through the third level cache, the proxy object is still not initialized! Then temporarily put the proxy objects in the second level cache, and then delete the proxy object was in the third grade in the cache data (late will not be generated every time A new proxy objects), behind the other classes should use A, went to the second level cache, acquiring A proxy object, and they all use the same A proxy object, Only this one proxy object needs to be refined later, and all other classes that introduce this proxy object will be perfected.

Further to the back, continue to complete the initialization of A, so first judge whether A exists in earlyProxyReferences, there is no need to experience AOP again AOP. So the operation of A is transformed from the level 2 cache, takes the proxy class of A, and populates the properties of the proxy class.

After completion, the proxy object of A is added to the first level cache, and its data originally in the second level cache is deleted, to ensure that the class of A is used later, directly from the first level cache.

So let’s look at a picture

conclusion

Having said that, the following three levels of caching are summarized:

Level 1 cache (also called singleton pool) : Map

singletonObjects, which holds Bean objects that have gone through a full life cycle
,>

Level 2 cache: Map

earlySingletonObjects, which holds early exposed Bean objects whose life cycle is not over (their properties have not been filled), which may be proxy objects or primitive objects
,>

Map

> singletonFactories, which stores factories that can generate beans. Factories are used to generate proxy objects for beans
,>

🎉 Attached: a complete Spring loop dependent sequence diagram

Sequence diagrams are not standard, but they are easy to understand 🙃 When understanding cyclic dependencies, the whole thing is recursive, you have to have a sense of a dream within a trap

Due to typography and image compression reasons, 🤩🤩🤩 follow my official account: Berry ratweed, send keywords: cycle dependent sequence diagram to obtain ultra HD sequence diagram!

🎉 🎉 🎉 🎉 another special welfare 🤩 🤩 🤩 concern my public number: rats berries grass, send key word: circular dependencies Can obtain fine PDF version of this article!