Spring 6 Circular Dependencies and Early Proxies: The True Boundaries of Three-Level Caching
Baseline: Spring Framework 6, Spring Boot 3. This article explains a specific proxy inconsistency case and does not generalize the conclusion to all Beans.
1. Problem Model
Typical circular dependency:
A -> B -> Repository -> A
If dependency injection occurs before the Bean is fully initialized, Spring may expose references early through the three-level cache. Subsequent Bean initialization may also go through BeanPostProcessor to create proxies, resulting in:
Early Reference != Final Bean
2. What Three-Level Caching Can Do
The ObjectFactory in the three-level cache can provide early references. Post-processors implementing SmartInstantiationAwareBeanPostProcessor have the opportunity to participate in early proxy through getEarlyBeanReference.
AbstractAutoProxyCreator records early proxy references to avoid creating the same auto-proxy multiple times after initialization. However, not every BeanPostProcessor participates in early references.
Regular post-processors that only wrap Beans in the postProcessAfterInitialization phase may cause identity inconsistency between early references and final objects. Whether this actually occurs depends on the Spring version, post-processor order, and proxy type.
3. Spring Boot 3 Recommendations
Since Spring Boot 2.6, circular references are handled more strictly by default. Boot 3 projects should prioritize removing circular dependencies and should not use spring.main.allow-circular-references=true as a default fix.
Recommended order:
- Split responsibilities between Services and Repositories;
- Use events or interface inversion to reverse dependency direction;
- Use @Lazy for dependencies that genuinely need delayed creation and evaluate the lifecycle;
- Check whether transactions, caching, async, exception translation, and custom post-processors create proxies.
4. Troubleshooting Methods
Record the actual Spring Framework version; check the Bean proxy types and Advisors; separately track getEarlyBeanReference and postProcessAfterInitialization; confirm whether multiple post-processors are wrapping repeatedly; check allowCircularReferences and BeanDefinition order.
The three-level cache is not a guarantee that "all circular dependencies can be solved." It's merely a mechanism for the container to provide early references at specific lifecycle stages. Ultimately, a clear and maintainable dependency graph is still required.