NOTE
Circular Dependencies
1. What a circular dependency is 2. What problems it causes 3. How to solve it 4. References
This is a historical learning note and may contain outdated or incomplete understanding.
1. What Is a Circular Dependency?
Suppose there are two Beans, Bean1 and Bean2. If Bean1 depends on Bean2, and Bean2 depends on Bean1, then when Spring creates Bean1 it first needs to create Bean2, while creating Bean2 requires creating Bean1 first. This is called a circular dependency.
2. What Problems Can a Circular Dependency Cause?
2.1. Constructor Injection Throws an Exception
- TestController
@Service
public class TestController
{
private TestService testService;
@Autowired
public TestController(TestService testService)
{
this.testService = testService;
}
public void test()
{
String byId = testService.getById(1);
System.out.println(byId);
}
}
- TestService
@Service
public class TestService
{
private TestController testController;
@Autowired
public TestService(TestController testController)
{
this.testController = testController;
}
public String getById(Integer id)
{
return "" + id;
}
}
After startup, an exception is thrown:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| testController defined in file [C:\Users\[user]\code\test\target\classes\com\example\test\controller\TestController.class]
↑ ↓
| testService defined in file [C:\Users\[user]\code\test\target\classes\com\example\test\service\TestService.class]
└─────┘
Process finished with exit code 1
3. How to Solve Circular Dependencies
3.1. Property / Setter Injection
- TestController
@Service
public class TestController
{
@Autowired// depends on TestService
private TestService testService;
public void test()
{
String byId = testService.getById(1);
System.out.println(byId);
}
}
- TestService
@Service
public class TestService
{
@Autowired// depends on TestController
private TestController testController;
public String getById(Integer id)
{
return "" + id;
}
}
3.1.1. Principle Analysis
Spring has three steps:
createBeanInstance: instantiate the object, which is essentially calling the object’s constructor.populateBean: populate properties. This step mainly populates dependency properties between Beans.initializeBean: invoke the init method in Spring XML. One step creates the instance, and another assigns property values (property injection happens in this step).
Using the example above, when Spring creates TestController:
createBeanInstancecreates theTestControllerinstance and puts it into a map cache.- When
populateBeanassignsTestServiceto theTestControllerproperty, it finds that there is noTestServiceinstance, so it first needs to create one.createBeanInstancecreates theTestServiceinstance.- When
populateBeanassignsTestControllerto theTestServiceproperty, the container already has aTestControllerinstance, so it takes it from the map cache and assigns it.
- Then execution returns to
populateBeanto assignTestServiceto theTestControllerproperty.
3.2. Use @PostConstruct
3.3. Implement ApplicationContextAware and InitializingBean
3.4. Add @Lazy to Both Constructors
- TestController
@Service
public class TestController
{
private TestService testService;
@Lazy
@Autowired
public TestController(TestService testService)
{
this.testService = testService;
}
public void test()
{
String byId = testService.getById(1);
System.out.println(byId);
}
- TestService
@Service
public class TestService
{
private TestController testController;
@Autowired
@Lazy
public TestService(TestController testController)
{
this.testController = testController;
}
public String getById(Integer id)
{
return "" + id;
}
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub