introduction
Have you encountered such error messages in Spring Boot development?
The dependencies of some of the beans in the application context form a cycle
This means that your application has a circular dependency. Although the Spring framework solves some circular dependency problems through clever mechanisms, developers still need to be wary of such problems in actual development (especially when using constructor injection). This article will explore the root causes of circular dependencies in depth, analyze Spring's solution strategies, and provide a variety of practical solutions.
1. What is circular dependency
Circular dependence refers to two or more beans relying on each other to form a closed loop. For example:
- The creation of Bean A requires injection of Bean B
- The creation of Bean B needs to be injected again Bean A
At this time, the Spring container will fall into a "dead loop" when initializing the bean. Here is a typical example:
@Service public class ServiceA { private final ServiceB serviceB; public ServiceA(ServiceB serviceB) { //Constructor injection service = serviceB; } } @Service public class ServiceB { private final ServiceA serviceA; public ServiceB(ServiceA serviceA) { //Constructor injection service = serviceA; } }
When starting the application, Spring throws an exception:
BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Requested bean is currently in creation
2. How to solve circular dependencies in Spring
Spring solves the circular dependency problem of singleton beans through a three-level caching mechanism:
- SingletonObjects: Stores fully initialized beans.
- Line Secondary Cache (earlySingletonObjects): Stores semi-finished beans exposed in advance (instated only, without properties populated).
- SingletonFactories: A factory object that stores beans, used to generate semi-finished beans.
Solution process (taking A and B interdependence as an example):
- When creating A, first instantiate A (the property is not filled) and put A's factory into Level 3 cache.
- When filling the properties of A, I found that B is needed and I started creating B.
- When creating B, after instantiating B, it is found that A is needed. At this time, the semi-finished object of A is obtained through the factory from the third-level cache.
- B completes initialization and puts it in the first-level cache.
- A continues to fill the instance of B, completes initialization, and puts it in the first-level cache.
Key limitations: This mechanism only supports singleton beans and injected through attributes. Constructor injection will fail directly!
3. Why constructor injection causes circular dependency failure
Constructor injection requires that the Bean obtains dependency objects immediately during the instantiation stage, while the third-level caching mechanism needs to resolve dependencies during the property injection stage. Therefore, when both beans are injected using constructors, Spring cannot expose the semi-finished beans in advance, resulting in the inability to resolve the cycle dependency.
Four. Solution: Four ways to break circular dependencies
1. Use Setter/Field injection instead (use with caution)
Change constructor injection to Setter or field injection, allowing Spring to delay injection of dependencies:
@Service public class ServiceA { private ServiceB serviceB; @Autowired // Setter injection public void setServiceB(ServiceB serviceB) { = serviceB; } }
Advantages: Quickly solve problems.
Disadvantages: Break immutability (fields are not final) and may mask design problems.
2. Use @Lazy to lazy load
Add @Lazy on the dependency object to tell Spring to delay initialization of the bean:
@Service public class ServiceA { private final ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { = serviceB; // The actual injected is the proxy object } }
Principle: Spring generates proxy objects, and the target bean will be truly initialized only when it is called for the first time.
Applicable scenarios: Solve the circular dependency of constructor injection.
3. Redesign the code structure
Eliminate circular dependencies by hierarchy or extracting public logic:
Scheme 1: Introduce an intermediate layer (such as ServiceC) to transfer the common dependencies of A and B to C.
Solution 2: Use event-driven (ApplicationEvent) to decouple direct dependencies.
// Event-driven example@Service public class ServiceA { @Autowired private ApplicationEventPublisher eventPublisher; public void doSomething() { (new EventA()); } } @Service public class ServiceB { @EventListener public void handleEventA(EventA event) { // Handle events } }
4. Use ObjectProvider (recommended)
Inject ObjectProvider into the constructor and get the dependencies as needed:
@Service public class ServiceA { private final ServiceB serviceB; public ServiceA(ObjectProvider<ServiceB> serviceBProvider) { = (); } }
Advantages: Maintain immutability of constructor injection and explicitly control the dependency acquisition timing.
Note: Make sure that the dependency bean exists and is unique.
V. Best practices and preventive measures
1. Priority to constructor injection: Maintain the immutability and explicit dependencies of the bean, but beware of circular dependencies.
2. Regularly detect cyclic dependencies:
Use IDE plugins (such as IntelliJ's Circular Dependencies analysis).
Through Maven/Gradle plug-ins (such as spring-boot-dependencies-analysis).
3. Code hierarchy specification:
Strictly follow the hierarchical architecture (Controller → Service → Repository).
Avoid interdependence of beans within the same layer.
4. Unit test verification: Write integration tests to verify the initialization process of the bean.
@SpringBootTest public class CircularDependencyTest { @Autowired private ApplicationContext context; @Test void contextLoads() { // If there is no exception in startup, pass the test assertNotNull(()); } }
6. Summary
Circular dependency is a common pitfall in Spring development, and its essence is a code design problem. Although Spring provides some solutions, refactoring code to eliminate circular dependencies is the fundamental way. By rationally using injection methods, code hierarchy and tool detection, developers can effectively avoid such problems and build highly maintainable applications.
Remember:
- Use @Lazy and Setter injection with caution, they may mask design flaws.
- Constructor injection + reasonable layering = more robust system!
This is the article about common pitfalls and solutions for circular dependencies in SpringBoot. For more related SpringBoot circular dependencies, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!