Spring IoC Circular Dependency

Basic Introduction

Circular dependency is circular reference, which means two or more Beans mutually reference each other, ultimately forming a ring.

Note: This is not circular function calls, but mutual dependency relationships between objects. Circular calls are actually an infinite loop unless there is a termination condition.

Scenarios for circular dependency in Spring:

  • Constructor circular dependency (constructor injection)
  • Field property circular dependency (set method injection)

Among these, constructor circular dependency cannot be solved and can only throw BeanCurrentlyInCreationException. When solving property circular dependency, Spring uses the method of exposing objects early.

Processing Mechanism

  • Singleton Bean constructor parameter circular dependency (cannot be solved)
  • Prototype Bean circular dependency (cannot be solved)

Prototype Bean

During prototype Bean initialization, regardless of whether circular dependency occurs through constructor parameters or through setXxx methods, Spring directly reports an error.

In AbstractBeanFactory.doGetBean() method, there is this code. The isPrototypeCurrentlyInCreation function content is as follows:

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null &&
            (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

Before getting the Bean, if this Bean is being created, it directly throws an exception. The prototype Bean will be marked before creation, indicating this beanName is being created. After creation ends, the mark will be removed.

Summary: Spring does not support circular dependency for prototype Beans.


setXxx and Autowired

Spring’s circular dependency theory is based on Java’s reference passing. When obtaining an object’s reference, object properties can be set later. However, constructors must be before obtaining the reference.

Spring solves circular dependency through setXxx or Autowired method by exposing an ObjectFactory object early. Simply put, after ClassA completes object initialization through constructor and before calling ClassA’s setClassB method, ClassA’s instantiated object is exposed to Spring container early through ObjectFactory.

Spring container exposes ClassA to Spring container after initializing ClassA through constructor.

  • ClassA calls setClassB method. Spring first tries to get ClassB from the container. At this time, ClassB does not exist in Spring container.
  • Spring container initializes ClassB and also exposes ClassB to Spring container early.
  • ClassB calls setClassA method. Spring gets ClassA from the container. Because ClassA was exposed early in the first step, ClassA instance can be obtained.
  • Both ClassA and ClassB complete object initialization, solving the circular dependency problem.

Final Summary

Why Prototype Cannot Solve Circular Dependency

In Spring, when a Bean is defined with prototype scope, the circular dependency problem cannot be solved. This is because prototype-scoped Beans are not cached like singleton-scoped Beans. The Spring container cannot pre-instantiate and cache their references, making it impossible to complete dependency injection in circular dependency scenarios.

How Singleton Scope Solves Circular Dependency

In singleton scope, Spring container uses three-level cache to solve circular dependency:

  • First-level cache: Stores fully initialized singleton Beans (singletonObjects).
  • Second-level cache: Stores early exposed Bean instances (earlySingletonObjects), avoiding duplicate creation before full initialization.
  • Third-level cache: Stores Bean’s ObjectFactory for dynamically creating Beans.

When creating a Bean, if circular dependency is detected, Spring can solve the circular dependency problem by exposing references to Beans that are not yet fully initialized.

How to Solve Circular Dependency for Prototype Scope?

Since Spring cannot automatically solve the circular dependency problem for prototype scope, here are some common solutions:

Method 1: Refactor Design to Avoid Circular Dependency

Usually, circular dependency itself is a design problem that can be solved by modifying dependency relationships. For example:

  • Introduce a third-party service as intermediary.
  • Use event-driven or callback mechanisms instead of direct dependency.

Method 2: Manual Dependency Injection

Manually inject dependencies through code to avoid dependency injection during Bean initialization.

@Component
@Scope("prototype")
public class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

Method 3: Use @Lazy for Delayed Loading

Using delayed initialization so that dependencies are created only when needed, avoiding circular dependency during initialization.

@Component
@Scope("prototype")
public class A {
    @Autowired
    @Lazy
    private B b;
}

@Component
@Scope("prototype")
public class B {
    @Autowired
    @Lazy
    private A a;
}

Method 4: Through ObjectFactory or Provider Dynamic Access

Use Spring’s ObjectFactory or javax.inject.Provider to lazily get Beans.

@Component
@Scope("prototype")
public class A {
    @Autowired
    private ObjectFactory<B> bFactory;

    public B getB() {
        return bFactory.getObject();
    }
}

@Component
@Scope("prototype")
public class B {
    @Autowired
    private ObjectFactory<A> aFactory;

    public A getA() {
        return aFactory.getObject();
    }
}