Spring — Circular Dependencies in Spring | Code Factory

Code Factory
3 min readNov 2, 2020

Donate : Link

WordPress Blog : Link

Applications… : Link

1. What Is a Circular Dependency?

It happens when a bean A depends on another bean B, and the bean B depends on the bean A as well:

Bean A → Bean B → Bean A

Of course, we could have more beans implied:

Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. What Happens in Spring

  • When Spring context is loading all the beans, it tries to create beans in the order needed for them to work completely. For instance, if we didn’t have a circular dependency, like the following case:
  • Bean A → Bean B → Bean C
  • Spring will create bean C, then create bean B (and inject bean C into it), then create bean A (and inject bean B into it).
  • But, when having a circular dependency, Spring cannot decide which of the beans should be created first, since they depend on one another. In these cases, Spring will raise a BeanCurrentlyInCreationException while loading context.
  • * It can happen in Spring when using constructor injection; if you use other types of injections you should not find this problem since the dependencies will be injected when they are needed and not on the context loading.

3. A Quick Example

CircularDependencyA.java

package com.codeFactory;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author code.factory
*/
@Component
public class CircularDependencyA {
private CircularDependencyB circB; @Autowired
public CircularDependencyA(CircularDependencyB circB) {
this.circB = circB;
}
}

CircularDependencyB.java

package com.codeFactory;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author code.factory
*/
@Component
public class CircularDependencyB {
private CircularDependencyA circA; @Autowired
public CircularDependencyB(CircularDependencyA circA) {
this.circA = circA;
}
}

Description:

Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
| circularDependencyA defined in file [PROJECT_PATH\target\classes\com\codeFactory\CircularDependencyA.class]
↑ ↓
| circularDependencyB defined in file [PROJECT_PATH\target\classes\com\codeFactory\CircularDependencyB.class]
└─────┘

4. The Workarounds

We will show some of the most popular ways to deal with this problem.

4.1 Redesign

  • When you have a circular dependency, it’s likely you have a design problem and the responsibilities are not well separated. You should try to redesign the components properly so their hierarchy is well designed and there is no need for circular dependencies.
  • If you cannot redesign the components (there can be many possible reasons for that: legacy code, code that has already been tested and cannot be modified, not enough time or resources for a complete redesign…), there are some workarounds to try.

4.2 Use @Lazy

  • A simple way to break the cycle is saying Spring to initialize one of the beans lazily. That is: instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it’s first needed.
  • To try this with our code, you can change the CircularDependencyA to the following:
package com.codeFactory;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
/**
* @author code.factory
*/
@Component
public class CircularDependencyA {
private CircularDependencyB circB; @Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
  • If you run the test now, you will see that the error does not happen this time.

4.3 Use Setter/Field Injection

  • One of the most popular workarounds, and also what Spring documentation proposes, is using setter injection.
  • Simply put if you change the ways your beans are wired to use setter injection (or field injection) instead of constructor injection — that does address the problem. This way Spring creates the beans, but the dependencies are not injected until they are needed.
package com.codeFactory;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author code.factory
*/
@Component
public class CircularDependencyA {
private CircularDependencyB circB; public CircularDependencyB getCircB() {
return circB;
}
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
}

4.4 Use @PostConstruct

  • Another way to break the cycle is injecting a dependency using @Autowired on one of the beans, and then use a method annotated with @PostConstruct to set the other dependency.

CircularDependencyA.java

package com.codeFactory;import javax.annotation.PostConstruct;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author code.factory
*/
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
@PostConstruct
public void init() {
circB.setCircA(this);
}
public CircularDependencyB getCircB() {
return circB;
}
}

CircularDependencyB.java

package com.codeFactory;import org.springframework.stereotype.Component;/**
* @author code.factory
*/
@Component
public class CircularDependencyB {
private CircularDependencyA circA; public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public CircularDependencyA getCircA() {
return circA;
}
}

--

--