Spring Circular Dependency Unit Tests: Full Analysis and Hands‑On Demo

This article provides a comprehensive set of JUnit 5 unit tests that demonstrate how Spring Boot 3.x and Spring Framework 6.x handle various circular dependency scenarios—including field, setter, constructor, @Lazy, prototype beans, three‑bean cycles, and AOP proxy interactions—along with Maven and IDE run instructions.

CodeSmart Hoops
CodeSmart Hoops
CodeSmart Hoops
Spring Circular Dependency Unit Tests: Full Analysis and Hands‑On Demo
Version Info: Spring Boot 3.x + Spring Framework 6.x, JDK 17+, Test framework JUnit 5 + Spring Boot Test. Corresponding article: "Spring Circular Dependency Full Analysis".

Environment Setup

Maven Dependencies (pom.xml)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
    <relativePath/>
</parent>
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- Spring Boot AOP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- Spring Boot Test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Test Class

CircularDependencyTest – Circular Dependency Tests

Test Content :

Field injection (setter) circular dependency – resolvable

Constructor circular dependency – not resolvable

@Lazy resolves constructor circular dependency

Prototype bean circular dependency – not resolvable

Three‑bean circular dependency – resolvable

AOP proxy interaction with circular dependency

Specific Code :

package com.example.springcontainer.circular;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.*;

/**
 * Circular dependency tests verifying Spring's handling in different scenarios:
 * - Field injection (setter) circular dependency: resolvable
 * - Constructor circular dependency: not resolvable
 * - @Lazy resolves constructor circular dependency
 * - Prototype Bean circular dependency: not resolvable
 * - Three Bean circular dependency: resolvable
 * - AOP proxy interaction with circular dependency
 */
@DisplayName("循环依赖测试")
class CircularDependencyTest {

    // ---------- Field injection circular dependency ----------
    @Component
    static class FieldInjectA {
        @Autowired
        private FieldInjectB b;
        FieldInjectB getB() { return b; }
    }
    @Component
    static class FieldInjectB {
        @Autowired
        private FieldInjectA a;
        FieldInjectA getA() { return a; }
    }

    // ---------- Setter injection circular dependency ----------
    @Component
    static class SetterInjectA {
        private SetterInjectB b;
        @Autowired
        public void setB(SetterInjectB b) { this.b = b; }
        SetterInjectB getB() { return b; }
    }
    @Component
    static class SetterInjectB {
        private SetterInjectA a;
        @Autowired
        public void setA(SetterInjectA a) { this.a = a; }
        SetterInjectA getA() { return a; }
    }

    // ---------- Constructor circular dependency ----------
    @Component
    static class ConstructorA {
        private final ConstructorB b;
        @Autowired
        public ConstructorA(ConstructorB b) { this.b = b; }
        ConstructorB getB() { return b; }
    }
    @Component
    static class ConstructorB {
        private final ConstructorA a;
        @Autowired
        public ConstructorB(ConstructorA a) { this.a = a; }
        ConstructorA getA() { return a; }
    }

    // ---------- @Lazy resolves constructor circular dependency ----------
    @Component
    static class LazyA {
        private final LazyB b;
        @Autowired
        public LazyA(@Lazy LazyB b) { this.b = b; }
        LazyB getB() { return b; }
    }
    @Component
    static class LazyB {
        private final LazyA a;
        @Autowired
        public LazyB(LazyA a) { this.a = a; }
        LazyA getA() { return a; }
    }

    // ---------- Prototype Bean circular dependency ----------
    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    static class PrototypeA {
        @Autowired
        private PrototypeB b;
        PrototypeB getB() { return b; }
    }
    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    static class PrototypeB {
        @Autowired
        private PrototypeA a;
        PrototypeA getA() { return a; }
    }

    // ---------- Three Bean circular dependency ----------
    @Component
    static class ThreeBeanA {
        @Autowired
        private ThreeBeanB b;
        ThreeBeanB getB() { return b; }
    }
    @Component
    static class ThreeBeanB {
        @Autowired
        private ThreeBeanC c;
        ThreeBeanC getC() { return c; }
    }
    @Component
    static class ThreeBeanC {
        @Autowired
        private ThreeBeanA a;
        ThreeBeanA getA() { return a; }
    }

    // ---------- AOP proxy + circular dependency ----------
    @Service
    static class AopServiceA {
        @Autowired
        private AopServiceB b;
        @Transactional
        public String doSomething() { return "A did something, B says: " + b.doOther(); }
        public String simple() { return "A simple"; }
        public AopServiceB getB() { return b; }
    }
    @Service
    static class AopServiceB {
        @Autowired
        private AopServiceA a;
        @Transactional
        public String doOther() { return "B did other, A says: " + a.simple(); }
        public String simple() { return "B simple"; }
        public AopServiceA getA() { return a; }
    }
    @Configuration
    @EnableAspectJAutoProxy
    static class AopConfig { /* @Transactional needs AOP proxy, triggers three‑level cache interaction */ }

    // ---------- Test cases ----------
    @Test
    @DisplayName("测试1:字段注入循环依赖可以被解决")
    void testFieldInjectCircularDependency() {
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FieldInjectA.class, FieldInjectB.class)) {
            FieldInjectA a = ctx.getBean(FieldInjectA.class);
            FieldInjectB b = ctx.getBean(FieldInjectB.class);
            assertNotNull(a);
            assertNotNull(b);
            assertSame(b, a.getB());
            assertSame(a, b.getA());
        }
    }

    @Test
    @DisplayName("测试2:Setter 注入循环依赖可以被解决")
    void testSetterInjectCircularDependency() {
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SetterInjectA.class, SetterInjectB.class)) {
            SetterInjectA a = ctx.getBean(SetterInjectA.class);
            SetterInjectB b = ctx.getBean(SetterInjectB.class);
            assertNotNull(a);
            assertNotNull(b);
            assertSame(b, a.getB());
            assertSame(a, b.getA());
        }
    }

    @Test
    @DisplayName("测试3:构造器循环依赖抛出 BeanCurrentlyInCreationException")
    void testConstructorCircularDependencyThrows() {
        assertThrows(Exception.class, () -> {
            try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConstructorA.class, ConstructorB.class)) {
                ctx.getBean(ConstructorA.class);
            }
        });
    }

    @Test
    @DisplayName("测试4:@Lazy 解决构造器循环依赖")
    void testLazyAnnotationResolvesConstructorCircularDependency() {
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(LazyA.class, LazyB.class)) {
            LazyA a = ctx.getBean(LazyA.class);
            LazyB b = ctx.getBean(LazyB.class);
            assertNotNull(a);
            assertNotNull(b);
            assertNotNull(a.getB()); // proxy resolves lazily
            assertSame(a, b.getA());
        }
    }

    @Test
    @DisplayName("测试5:prototype Bean 循环依赖无法解决")
    void testPrototypeCircularDependencyThrows() {
        assertThrows(Exception.class, () -> {
            try (var ctx = new AnnotationConfigApplicationContext(PrototypeA.class, PrototypeB.class)) {
                ctx.getBean(PrototypeA.class);
            }
        });
    }

    @Test
    @DisplayName("测试6:三 Bean 循环依赖(A→B→C→A)可以被解决")
    void testThreeBeanCircularDependency() {
        try (var ctx = new AnnotationConfigApplicationContext(ThreeBeanA.class, ThreeBeanB.class, ThreeBeanC.class)) {
            ThreeBeanA a = ctx.getBean(ThreeBeanA.class);
            ThreeBeanB b = ctx.getBean(ThreeBeanB.class);
            ThreeBeanC c = ctx.getBean(ThreeBeanC.class);
            assertNotNull(a);
            assertNotNull(b);
            assertNotNull(c);
            assertSame(b, a.getB());
            assertSame(c, b.getC());
            assertSame(a, c.getA());
        }
    }

    @Test
    @DisplayName("测试7:AOP 代理与循环依赖的交互 - 三级缓存必要性")
    void testAopProxyAndCircularDependency() {
        try (var ctx = new AnnotationConfigApplicationContext(AopServiceA.class, AopServiceB.class, AopConfig.class)) {
            AopServiceA a = ctx.getBean(AopServiceA.class);
            AopServiceB b = ctx.getBean(AopServiceB.class);
            assertNotNull(a);
            assertNotNull(b);
            String result = a.doSomething();
            assertNotNull(result);
            assertTrue(result.contains("A did something"));
            assertTrue(result.contains("B did other"));
            // Verify that AOP proxies are obtained from the third‑level cache
            assertSame(b, a.getB());
            assertSame(a, b.getA());
        }
    }
}

Running the Tests

Method 1: Maven Command

# Run a single test class
mvn test -Dtest=CircularDependencyTest

Method 2: IDEA

Open any test class.

Right‑click the class name or method name.

Select "Run 'XXX'" or "Debug 'XXX'".

Related Documents

Spring Circular Dependency Full Analysis – complete principle document.

Spring Circular Dependency Interview Self‑Test and Answers – interview preparation material.

Applicable Versions: Spring Boot 3.x + Spring Framework 6.x + JDK 17+

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaAOPSpringSpring BootCircular DependencyUnit TestPrototype Bean
CodeSmart Hoops
Written by

CodeSmart Hoops

A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.