Keywords: Spring Framework | Circular Dependency | BeanCurrentlyInCreationException | View Scope | Dependency Injection
Abstract: This article provides an in-depth analysis of circular dependency issues in non-singleton scopes (such as view scope) within the Spring Framework. Through concrete case studies, it demonstrates the triggering scenarios of BeanCurrentlyInCreationException, explains the different handling mechanisms of Spring's three-level cache for singleton and non-singleton beans, and offers effective solutions using @Lazy annotation and @PostConstruct initialization methods, while also discussing the design problems behind circular dependencies.
Problem Background and Phenomenon Description
During Spring Framework application development, circular dependency issues may arise when two or more beans have mutual dependencies. Particularly when using non-singleton scopes (such as view scope, session scope, etc.), Spring's default circular dependency resolution mechanism may not function properly, leading to BeanCurrentlyInCreationException.
Consider the following typical scenario: two view-scoped beans depend on each other. Bean1 injects Bean2 via @Autowired, while Bean2 similarly injects Bean1 via @Autowired. This configuration causes circular dependency detection to fail during Spring container initialization.
@Component("bean1")
@Scope("view")
public class Bean1 {
@Autowired
private Bean2 bean2;
}
@Component("bean2")
@Scope("view")
public class Bean2 {
@Autowired
private Bean1 bean1;
}When the application attempts to access a page using these beans, the Spring container throws an exception: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bean1': Requested bean is currently in creation: Is there an unresolvable circular reference?
In-Depth Analysis of Spring Dependency Injection Mechanism
To understand the root cause of this issue, it is essential to delve into the bean creation and dependency injection mechanisms of the Spring Framework. Spring manages the creation process of singleton beans through a three-level cache system:
- First-level cache (singletonObjects): Stores fully initialized and available singleton bean instances
- Second-level cache (earlySingletonObjects): Stores early bean references that have been instantiated but not yet completed property injection
- Third-level cache (singletonFactories): Stores bean factory objects used to create early bean references
For singleton-scoped beans, Spring can resolve most circular dependency issues (excluding constructor injection) through this three-level cache mechanism. When BeanA depends on BeanB, and BeanB in turn depends on BeanA, Spring places a semi-constructed reference of BeanA into the second-level cache during BeanA's creation process, then proceeds to create BeanB. When BeanB needs to inject BeanA, it can retrieve this semi-constructed reference from the second-level cache, thus completing the dependency injection.
However, this mechanism does not apply to non-singleton scoped beans (such as view scope, prototype scope, etc.). The lifecycle management of non-singleton beans fundamentally differs from that of singleton beans:
public class ViewScope implements Scope {
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
// Other Scope interface method implementations...
}In view scope, bean instances are stored in JSF's view map, and new instances may be created with each request. Spring's three-level cache is primarily designed for singleton beans and cannot provide circular dependency solutions for beans of this scope.
Solutions and Practices
For circular dependency issues in non-singleton scopes, the following effective solutions are available:
Solution 1: Using @Lazy Annotation
The @Lazy annotation can delay dependency initialization, breaking the immediacy of circular dependencies. By adding the @Lazy annotation to one of the dependent fields, Spring creates a proxy object instead of immediately initializing the target bean:
@Component("bean1")
@Scope("view")
public class Bean1 {
@Autowired
@Lazy
private Bean2 bean2;
}
@Component("bean2")
@Scope("view")
public class Bean2 {
@Autowired
private Bean1 bean1;
}This method is simple and effective, but note the compatibility of the @Lazy annotation in Spring 3. In some Spring 3 versions, additional configuration may be required for proper functionality.
Solution 2: Using @PostConstruct for Manual Injection
By using the @PostConstruct annotation to manually set dependencies after bean initialization, you can avoid Spring detecting circular dependencies during the property injection phase:
@Component("bean1")
@Scope("view")
public class Bean1 {
@Autowired
private Bean2 bean2;
@PostConstruct
public void init() {
bean2.setBean1(this);
}
}
@Component("bean2")
@Scope("view")
public class Bean2 {
private Bean1 bean1;
public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}
}The advantage of this method is that it completely avoids Spring's circular dependency detection mechanism. Bean2 no longer directly depends on Bean1 via @Autowired, but establishes the relationship through method invocation after Bean1 initialization.
Solution 3: Refactoring Design to Eliminate Circular Dependencies
From a software design perspective, circular dependencies typically indicate unclear responsibility division between classes. Consider the following refactoring solutions:
- Extract Common Interfaces: Extract mutually dependent parts as interfaces to reduce coupling
- Introduce Mediator Pattern: Use a third class to coordinate interactions between two beans
- Use Event-Driven Approach: Implement loose coupling communication through Spring's event mechanism
Technical Details and Considerations
When implementing the above solutions, pay attention to the following technical details:
Correctness of Scope Configuration: Ensure proper configuration of custom scopes. View scope needs to work with web frameworks like JSF; improper configuration may cause scope management to fail.
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="view">
<bean class="${project.groupId}.utils.ViewScope" />
</entry>
</map>
</property>
</bean>Choice of Injection Annotations: Avoid mixing @Autowired and @Resource annotations. Although Spring supports multiple injection methods, mixing them may lead to unpredictable initialization order and increase the risk of circular dependencies.
Understanding Bean Lifecycle: Deep understanding of the Spring bean lifecycle is crucial for solving complex dependency issues. The bean creation process includes multiple stages such as instantiation, property injection, and initialization, with different dependency handling strategies at each stage.
Summary and Best Practices
Circular dependency issues in the Spring Framework are particularly prominent in non-singleton scopes because the three-level cache mechanism is primarily designed for singleton beans. Solutions such as the @Lazy annotation and @PostConstruct manual injection can effectively address these issues, but the most fundamental solution is to avoid circular dependencies through good design.
In actual project development, it is recommended to:
- Prioritize eliminating circular dependencies through design refactoring
- Choose appropriate solutions based on specific scenarios when circular dependencies cannot be avoided
- Thoroughly test various edge cases to ensure solution stability
- Maintain code clarity and maintainability, avoiding over-reliance on technical tricks
By deeply understanding Spring's dependency injection mechanism and bean lifecycle, developers can more confidently handle various complex dependency management scenarios and build more robust and maintainable applications.