NOTE

Circular Dependencies

1. What a circular dependency is 2. What problems it causes 3. How to solve it 4. References

JavaCreated Updated 1 min readhistorical

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:

  1. createBeanInstance: instantiate the object, which is essentially calling the object’s constructor.
  2. populateBean: populate properties. This step mainly populates dependency properties between Beans.
  3. 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:

  • createBeanInstance creates the TestController instance and puts it into a map cache.
  • When populateBean assigns TestService to the TestController property, it finds that there is no TestService instance, so it first needs to create one.
    • createBeanInstance creates the TestService instance.
    • When populateBean assigns TestController to the TestService property, the container already has a TestController instance, so it takes it from the map cache and assigns it.
  • Then execution returns to populateBean to assign TestService to the TestController property.

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;
    }
}

4. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub