Circular dependencies in Java

What are circular dependencies

For example, there are two classes

class A {
    private B b;
    public void setB(B b) {this.b = b;}
}


class B {
    private A a;
    public void setA(A a) {this.a = a;}
}
Copy the code

A objects need B objects, B objects need A objects, need each other, so it is called cyclic dependency

How do I create looping dependent objects

To create an object that the above loop depends on, do this

public static void main(String[] args) { A a = new A(); B b = new B(); // dependency injection a.setb (b); b.setA(a); }Copy the code

The idea is to instantiate first and set dependencies later

As shown above, this is the result of a run. To make it easier for you to understand the creation process of cyclic dependent objects, I have drawn a picture

  • After executing the first line of code: there is an instance of class A in the heap, A@491, where property B is null
  • After executing the second line of code: there is an instance of class B in the heap, B@492, where attribute A is null
  • After executing the third sentence: the attribute B of A@491 in the heap points to B@492
  • After executing the fourth line of code: the attribute a of B@492 in the heap points to A@491

There is A B in A and A in B, which is reflected in the heap. In fact, there is A reference to B@492 in A@491 and A reference to A@491 in B@492

So we’ve created the object that the loop depends on

What circular dependencies can’t be resolved

A cyclic dependency is unsolvable if its object cannot be instantiated and then set!

For example, A and B set dependencies through constructors

class A { private B b; public A(B b) { this.b = b; } } class B { private A a; public B(A a) { this.a = a; }}Copy the code

This circular dependency, when instantiated, needs to depend on the object, and the dependent object needs to instantiate itself, so this circular dependency is unsolvable!

This means that not all cyclic dependencies can be solved!

Spring loop dependencies

Spring addresses cyclic dependencies in the same way as above, instantiating and setting dependencies later. Also, there is no solution to loop dependencies created through constructors!

What scenarios can Spring solve for loop dependencies

Condition 1: Dependency injection mode!

In Spring, dependency injection comes in three ways

For more information about Spring dependency injection, see Spring dependency Injection

  1. Construction-based dependency injection
  2. Based on thesetter()Method dependency injection
  3. Dependency injection based on member variables

The first construction-based dependency injection, as mentioned above, cannot be solved if circular dependencies are created, while the other two can be solved by instantiating and setting dependencies later

Condition 2:BeanScope

For details about the Scope of a Bean, see Spring Bean Scope

In Spring, the Scope of a Bean will directly affect the creation behavior of the Bean, and the creation behavior determines whether the problem of loop dependency can be solved. Among the five scopes of the Bean, only the creation behavior of singleton Scope. When the problem of loop dependency occurs, The Spring framework can do this automatically for you, provided condition one is met

conclusion

When cyclic dependencies occur, the Spring framework automatically takes care of them, but there are two conditions

  1. BeanBased on theMember variablesorsetter()Method to inject dependencies
  2. The Scope of the Bean must be singleton

The lifecycle of the Spring Singleton Bean

Since the cycle dependency of a Bean can only be resolved when a Bean is created in the Singleton scope, it is important to understand the process of creating a Bean. I wrote a separate article referring to the life cycle of a Spring Singleton Bean

Spring Bean loop dependency case study

Our two classes, A and B, depend on each other

@Service
class B {
    private A a;

    @Autowired
    public void setA(A a) {this.a = a;}
}

@Service
class A {
    private B b;

    @Resource
    public void setB(B b) {this.b = b;}
}
Copy the code

To solve circular dependencies, you must inject dependencies based on member variables or setter() methods. Spring does not recommend injecting dependencies based on member variables, so I’m using setter() methods. The @autoWired and @Resource annotations are specifically used here, both of which can be auto-assembled with the same effect

Now let’s look at the process in the lifecycle of the Spring Singleton Bean to tease out how Spring resolves this circular dependency

If spring creates A Bean first, doGetBean() will be called in the singleton scope to fetch the Bean from the cache

  1. calldoGetBean(nameA,...)Method to get/create beanA
  2. First callgetSingleton(nameA)If you find it, you return it. The level 3 cache is still empty, so you can’t find it
  3. Call if not foundgetSingleton(nameA, ()->{createBean(nameA,...) })To create
  4. getSingleton()Method is called internallycreateBean(nameA,...)Create beanA
  5. createBean(nameA,...)Method is called internally firstcreateBeanInstance(nameA,...)At this time, although there is a beanA object, the property B inside beanA is still null, which needs to be filled later
  6. calladdSingletonFactory(nameA, () -> getEarlyBeanReference(nameA, mbd, beanA))Method puts anonymous functions (bean factories) that can get beanA or beanA proxy objects into the third-level cache
  7. callpopulateBean(nameA, mbd, beanA)Method populates beanA’s property A, which will eventually be called inside the methoddoGetBean(nameB,...)Method to get/create a beanB
    1. calldoGetBean(nameB,...)Method to get/create a beanB
    2. First callgetSingleton(nameB)If a beanA factory method is found in the third level cache, it will not be found
    3. Call if not foundgetSingleton(nameB, ()->{createBean(nameB,...) })To create
    4. getSingleton()Method is called internallycreateBean(nameB,...)Create the beanB
    5. createBean(nameB,...)Method is called internally firstcreateBeanInstance(nameB,...)At this time, although there is a beanB object, the attribute A inside beanB is still null and needs to be filled later
    6. calladdSingletonFactory(nameB, () -> getEarlyBeanReference(nameB, mbd, beanB))Method puts anonymous functions (bean factories) that can get beanB or beanB proxy objects into the third-level cache
    7. callpopulateBean(nameB, mbd, beanB)Method populates beanB’s property A, which is eventually called inside the methoddoGetBean(nameB,...)Method to get beanA
      1. calldoGetBean(nameA,...)Method to get/create beanA
      2. First callgetSingleton(nameA)The third level cache has beanA’s factory in it, so it pulls the factory out of the third level cache, calls the fetch method, and gets beanA’s “pre-reference”. In this case, since NO AOP was used on A, it gets the original beanA. Otherwise, I’m going to be represented by beanA
      3. Delete beanA’s factory from level 3 cache, and place beanA’s “pre-reference” in level 2 cache
      4. Return beanA’s “advance reference”
    8. At this point, beanB’s attributes are filled in, and attribute A is an “advance reference” to beanA. And then callinitializeBean(nameB, beanB, mbd)Initialize
    9. The created beanB is put into the first level cache, and the contents of the second and third levels of the beanB are deleted
    10. Returns the beanB
  8. At this point, beanA’s attributes are filled in, and attribute B is beanB in the first level cache. And then callinitializeBean(nameA, beanA, mbd)Initialize
  9. The created beanA is put into the first level cache, and the contents of the second and third level caches are deleted
  10. Return beanA

This is the case when A and B are not proxied by AOP. If they are proxied, they are almost the same. The only difference is that when you get the bean factory from the third level cache and call the fetch method, you get the bean’s proxy object instead of the original bean object