Analysis and Solutions for Circular Dependency Issues in Non-Singleton Scopes within Spring Framework

Nov 22, 2025 · Programming · 14 views · 7.8

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:

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:

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:

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.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.