Methods to solve circular dependency

catalogue

1, What is circular dependency?

2, Circular dependency of Spring

3, Take a chestnut

4, Solution

4.1 redesign

4.2 using setter/field method to inject

four point three   Use @ Lazy

4.4 use @ PostConstruct

4.5 implement ApplicationContextAware and InitializingBean interface

4.6 other methods

5, Summary

1, What is circular dependency?

Class a depends on class B, and class B also depends on class A. in this case, circular dependency will occur.

Bean A → Bean B → Bean A

The above are easy to find circular dependencies, and there are also deeper circular dependencies.

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

2, Circular dependency of Spring

When the Spring context loads all bean s, it tries to create them in the order of their associations. If there is no circular dependency, for example:

Bean A → Bean B → Bean C

Spring will first create Bean C, then create Bean B (and inject Bean C into Bean B), and finally create Bean A (and inject Bean B into Bean A).

However, if we have circular dependencies, the Spring context does not know which Bean should be created first because they depend on each other. In this case, Spring will throw a beancurrentyincreationexception when loading the context.

This is the case when we use constructors for injection. Because it is context loading, it is required to inject.


 

3, Take a chestnut

The user class needs to call the methods in the organization class, so it injects the organization class through the construction method.

@Service
public class UserService {
    
    private final DepartmentService departmentSerivce;

    /**
     * Inject the DepartmentService class through the construction method
     */
    @Autowired
    public UserSerivce(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    public List<Department> list() {
        retern departmentService.list();
    }    

}

The organization class just needs to call the methods in the user class, so it also injects the user class through the construction method.

@Service
public class DepartmentService {
    
    private final UserService userSerivce;

    /**
     * Inject UserService class through constructor
     */
    @Autowired
    public DepartmentSerivce(UserService userSerivce) {
        this.userSerivce = userSerivce;
    }
    
}

In this case, the program will report the following errors when compiling

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  userService defined in file [D:\Java\IdeaProjects\UserService.class]
↑     ↓
|  departmentService defined in file [D:\Java\IdeaProjects\DepartmentService.class]
└─────┘

Now we can write a configuration class for the test, let's call it TestConfig, which specifies the base package of the component to be scanned. Let's assume that our bean is defined in the package ‎ com.baeldung.circulardependency ‎:

@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}

Finally, we can write a JUnit test to check for cyclic dependencies. The test can be empty because a circular dependency will be detected during context loading. ‎

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {

    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
    }
}

If you try to run this test, you will get the following exception:

BeanCurrentlyInCreationException: Error creating bean with name 'UserService':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4, Solution

4.1 redesign

When this kind of cyclic dependency occurs, it is likely that there are problems in design and the responsibilities of each class are not well distinguished. You should try to redesign the component correctly so that its hierarchy is well designed and does not require circular dependencies. ‎

If redesign is not possible, other solutions can be considered.

4.2 using setter/field method to inject

As mentioned above, only the construction method is required to be injected when the context is loaded, which is prone to dependency loops. Therefore, dependency injection can be carried out in other ways. setter and field method injection are different from construction methods. They do not inject dependencies when creating beans, but only when needed.

setter method injection

@Service
public class UserService {
    
    private DepartmentService departmentSerivce;

   
    @Autowired
    public void setDepartmentSerivce(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    public List<Department> list() {
        retern departmentService.list();
    }    

}

The other class uses setter method injection in the same way

field method injection

(use @ Autowired)

@Service
public class UserService {
    
    @Autowired
    private DepartmentService departmentSerivce;


    public List<Department> list() {
        retern departmentService.list();
    }    

}

four point three   Use @ Lazy

A simple way to solve the Spring circular dependency is to use deferred loading on a Bean. In other words, the Bean is not fully initialized. In fact, it injects an agent, which will be fully initialized only when it is used for the first time.
We modified UserService and the results are as follows:

@Service
public class UserService {
    
    private DepartmentService departmentSerivce;

    @Autowired
    public UserSerivce(@Lazy DepartmentService departmentService) {
        this.departmentService = departmentService;
    }

    public List<Department> list() {
        retern departmentService.list();
    }    

}

If you run the test now, you will find that the previous error does not exist.

4.4 use @ PostConstruct

Another way to break the loop is to use @ Autowired on the attribute to be injected (the attribute is a bean) and mark it in another method with @ PostConstruct, and set dependencies on other methods in this method.

Our Bean will be modified to the following code:

@Service
public class UserService {
 
    @Autowired
    private DepartmentService departmentService;
 
    @PostConstruct
    public void init() {
        departmentService.setUserService(this);
    }
 
    public DepartmentService getDepartmentService() {
        return departmentService;
    }
}
@Service
public class DepartmentService {
 
    private UserService userService;
     
    private String message = "Hi!";
 
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
     
    public String getMessage() {
        return message;
    }
}

Now we run our modified code and find that no exceptions are thrown and rely on correct injection.

4.5 implement ApplicationContextAware and InitializingBean interface

If a bean implements ApplicationContextAware, the bean can access the Spring context and get other beans from there. The implementation of the InitializingBean interface indicates that the bean performs postprocessing operations after all the attributes are set up (the calling sequence is called after init-method). In this case, we need to set the dependency manually.

@Service
public class UserService implements ApplicationContextAware, InitializingBean {
 
    private DepartmentService departmentService;
 
    private ApplicationContext context;
 
    public DepartmentService getDepartmentService() {
        return departmentService;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(DepartmentService.class);
    }
 
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
public class DepartmentService {
 
    private UserService userService;
 
    private String message = "Hi!";
 
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
 
    public String getMessage() {
        return message;
    }
}

Similarly, we can run the previous test to see if there are exceptions thrown and whether the program results are what we expect.

4.6 other methods

The situation of this problem I encountered is   The interdependence between services leads to deep-seated circular dependency. But what I really want to call is the basic methods provided by Mybatis Plus, so I can replace the dependent Service with the dependent Mapper, because these basic methods are also available in Mapper.

@Service
public class UserService {
    
    private final DepartmentMapper departmentMapper;

    @Autowired
    public UserSerivce(DepartmentMapper departmentMapper) {
        this.departmentMapper = departmentMapper;
    }

    public List<Department> list() {
        retern departmentMapper.selectList();
    }    

}

5, Summary

In Spring, there are many ways to deal with circular dependencies. The first consideration is to redesign your bean s so that circular dependencies are not required: they are usually symptoms of designs that can be improved. However, if you absolutely need to have circular dependencies in your project, you can follow some of the solutions suggested here.

Reference link:

Chinese version: Circular dependency solution in spring - Jianshu (jianshu.com)

English version: Circular Dependencies in Spring | Baeldung

Keywords: Spring

Added by boushley on Wed, 01 Dec 2021 09:37:24 +0200