catalogue
1, What is circular dependency?
2, Circular dependency of Spring
4.2 using setter/field method to inject
4.5 implement ApplicationContextAware and InitializingBean interface
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