Spring circular dependency and its solution

Posted by wildmanmatt on Sat, 30 Oct 2021 16:10:37 +0200

1. What is circular dependency?

Bean A depends on B, and Bean B depends on A. in this case, circular dependency occurs.
Bean A → Bean B → Bean A
Circular dependencies caused by more complex indirect dependencies are as follows.
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. What is the result of circular dependency?

When Spring is loading all beans, Spring tries to create beans in the order that they can be created normally.
For example, there are the following dependencies:
Bean A → Bean B → Bean C
Spring first creates bean C, then creates bean B (injecting C into b), and finally creates bean A (injecting B into A).
However, when there is a circular dependency, Spring will not be able to decide which bean to create first. In this case, Spring will generate an exception, beancurrentyincreationexception.
Circular dependency often occurs when using constructor injection. This problem can be avoided if other types of injection are used.

3. Constructor injects circular dependency instance

First, define two bean s that depend on each other through the constructor injection.

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

Running the method givencirculardipendency_whenconstructorinjection_thenitfails will generate an exception: beancurrentyincreationexception: error creating bean with name 'circulardipendency a':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4. Solutions

There are several common ways to deal with this problem.

The best way: redesign the structure and eliminate circular dependency.

Other methods to solve circular dependency are as follows:
4.2 use annotation @ Lazy
One of the simplest ways to eliminate circular dependencies is by delaying loading. When injecting dependencies, inject proxy objects first, and then create objects to complete the injection when they are used for the first time.

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

After using @ Lazy, run the code and you can see the exception elimination.

4.3 using Setter/Field injection
One method suggested in the Spring documentation is to use setter injection. It is only injected when the dependency is finally used. Make less modifications to the sample code above to observe the test effect.

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

4.4 use @ PostConstruct

@Component
public class CircularDependencyA {
 
    @Autowired
    private CircularDependencyB circB;
 
    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
     
    private String message = "Hi!";
 
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
     
    public String getMessage() {
        return message;
    }
}

4.5 implement ApplicationContextAware and InitializingBean (executed after @ PostConstruct, so PostConstruct can solve the problem of circular dependency, and Initializing is also possible)

@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
 
    private CircularDependencyB circB;
 
    private ApplicationContext context;
 
    public CircularDependencyB getCircB() {
        return circB;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }
 
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

5. Summary

There are many ways to deal with circular dependencies. First, consider whether you can avoid circular dependencies by redesigning dependencies.
If circular dependency is really needed, it can be handled in the way mentioned above. setter injection is preferred.

Topics: Java Spring Back-end