SoFunction
Updated on 2025-04-17

Spring Boot circular dependency principle, solutions and best practices (full analysis)

🚨 Spring Boot cyclic dependency full analysis: principle, solution and best practice

#SpringBoot Core #Dependency Injection #Design Pattern #Performance Optimization

1. The nature and harm of circular dependence

1.1 What is circular dependency?

Circular dependencyIt refers to two or more beans referring directly or indirectly to each other to form a closed-loop dependency relationship.
Typical scenarios

@Service  
public class ServiceA {  
    @Autowired  
    private ServiceB serviceB;  
}  
@Service  
public class ServiceB {  
    @Autowired  
    private ServiceA serviceA;  
}  

An exception is thrown when Spring starts:

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

1.2 Core hazards

  • Application startup failed: Spring cannot complete bean initialization
  • Design defect signal: The module responsibilities are unclear and the coupling is too high
  • Potential performance issues: Even if circular dependencies are resolved, it may cause hidden initialization order issues

Pring cannot complete bean initializationDesign defect signal: The module responsibilities are unclear and the coupling is too highPotential performance issues: Even if circular dependencies are resolved, it may cause hidden initialization order issues

2. Spring's Level 3 Caching Mechanism

Spring solves through Level 3 cacheSetter/Field InjectionCircular dependency, but cannot be resolvedConstructor injectioncyclic dependency.

2.1 Level 3 cache structure

Cache Level Store content
SingletonObjects Fully initialized bean
Level 2 cache (earlySingletonObjects) Early exposure of early beans (unfinished attribute fill)
SingletonFactories Bean factory object (used to generate early references)

2.2 Solution process (taking ServiceA and ServiceB as examples)

1. createServiceA → Put the original object factory into the third level cache  
2. fillingServiceAproperty → Discover the needServiceB  
3. createServiceB → Put the original object factory into the third level cache  
4. fillingServiceBproperty → Get from Level 3 cacheServiceAFactory → Generate proxy objects  
5. ServiceBInitialization is completed → Move into Level 1 cache  
6. ServiceA继续fillingServiceB → Get from Level 1 cacheServiceB → Complete initialization  

3. Solution and code combat

3.1 Avoid constructor injection cycles

Constructor injection circular dependencies cannot be resolved(Spring 5.3+ is disabled by default):

// Error example: Start-up failed directly@Service  
public class ServiceA {  
    private final ServiceB serviceB;  
    public ServiceA(ServiceB serviceB) {  
         = serviceB;  
    }  
}  
@Service  
public class ServiceB {  
    private final ServiceA serviceA;  
    public ServiceB(ServiceA serviceA) {  
         = serviceA;  
    }  
}  

Forced to allow constructor cyclic dependencies (not recommended)

#   
-circular-references=true  

3.2 Injection using Setter/Field

Change constructor injection to Setter injection:

@Service  
public class ServiceA {  
    private ServiceB serviceB;  
    @Autowired  
    public void setServiceB(ServiceB serviceB) {  
         = serviceB;  
    }  
}  
@Service  
public class ServiceB {  
    private ServiceA serviceA;  
    @Autowired  
    public void setServiceA(ServiceA serviceA) {  
         = serviceA;  
    }  
}  

3.3 @Lazy Lazy Loading

Forced to delay the initialization of one of the beans:

@Service  
public class ServiceA {  
    @Lazy  
    @Autowired  
    private ServiceB serviceB;  
}  

principle: ServiceB is proxyed and will be actually initialized only when it is called for the first time.

3.4 Abstract decoupling of interfaces

Implement class dependencies through interface isolation

public interface IServiceA {  
    void doSomething();  
}  
public interface IServiceB {  
    void doAnother();  
}  
@Service  
public class ServiceAImpl implements IServiceA {  
    @Autowired  
    private IServiceB serviceB;  
}  
@Service  
public class ServiceBImpl implements IServiceB {  
    @Autowired  
    private IServiceA serviceA;  
}  

4. Deep optimization: Design mode application

4.1 Dependency inversion principle (DIP)

High-level modules should not rely on low-level modules, both should rely on abstraction

// Define the data access interfacepublic interface UserRepository {  
    User findById(Long id);  
}  
// High-level service dependency interface@Service  
public class UserService {  
    private final UserRepository repository;  
    public UserService(UserRepository repository) {  
         = repository;  
    }  
}  
// Low-level implementation@Repository  
public class JpaUserRepository implements UserRepository {  
    // Implementation details}  

4.2 Event-driven model

Decoupling of strong dependencies through ApplicationEvent

// ServiceA release event@Service  
public class ServiceA {  
    @Autowired  
    private ApplicationEventPublisher publisher;  
    public void triggerEvent() {  
        (new CustomEvent(this));  
    }  
}  
// ServiceB listens to events@Service  
public class ServiceB {  
    @EventListener  
    public void handleEvent(CustomEvent event) {  
        // Processing logic    }  
}  

V. Detection and prevention tools

5.1 IDE detection

  • IntelliJ IDEA: Automatically mark circular dependencies (need to be installedSpring AssistantPlugin)
  • Eclipse:passSTS (Spring Tool Suite)Plugin Tips

5.2 Maven plug-in analysis

<plugin>  
    <groupId></groupId>  
    <artifactId>spring-boot-maven-plugin</artifactId>  
    <configuration>  
        <excludes>  
            <exclude>  
                <groupId></groupId>  
                <artifactId>lombok</artifactId>  
            </exclude>  
        </excludes>  
    </configuration>  
</plugin>  

Run the command:

mvn spring-boot:run -=dev  

5.3 Architecture Specifications

  • Modular design: Split modules by business (such asuser-serviceorder-service
  • Reliance rules
    • The lower layer module can depend on the upper layer
    • The same level prohibits interdependence
    • General tools category sinks tocommonModule

6. FAQs

Q1: Will allowing cyclic dependencies have any impact on performance?

  • Short-term impact: Increase the context switch when bean creation
  • Long-term risk: May cause memory leaks (such as proxy objects not being properly released)

Q2: Can @Lazy annotation be used casually?

  • Use caution scenario: Frequently called beans will increase proxy overhead
  • Best Practice: Only for solving historical code that cannot be refactored

Q3: Why can Spring solve the problem of Setter injection circular dependencies?

  • Core mechanism: Level 3 cache exposes object references that have not been initialized in advance

7. Summary and best practices

Golden Rules

  • Preferred constructor injection: Forced exposure of dependencies
  • Comply with the principle of single responsibility: Split classes with more than 500 lines of code
  • Regular reliance review: Use ArchUnit and other tools to detect architecture specifications

Emergency repair process

Discover circular dependencies → Temporary solution with @Lazy → Mark as technical debt → Schedule refactoring

Tool recommendations

  • ArchUnit: Architectural rule detection
  • Spring Boot Actuator: Runtime dependency analysis

Through reasonable design + specification constraints, circular dependencies can be effectively avoided and a highly maintainable Spring Boot application can be built! 🚀

This is the article about the full analysis of Spring Boot circular dependencies: principles, solutions and best practices. For more related Spring Boot circular dependencies, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!