前置知识:下面先用一个从ApplicationContext中getBean的一个案例熟悉一下

用OkHttpClient注入到了ICO容器中并获取为例
2025-09-13T12:37:21.png

打断点进来发现,如果是无参构造的话则会执行if里面的,

2025-09-13T12:41:52.png

2025-09-13T12:42:03.png

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, 
                          @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    // 1. 解析 beanName(去掉别名,转为真正的名字)
    String beanName = this.transformedBeanName(name);

    // 2. 尝试从单例池里拿(一级缓存)
    Object sharedInstance = this.getSingleton(beanName);
    Object beanInstance;

    if (sharedInstance != null && args == null) {
        // 说明这个 Bean 已经创建过了(缓存命中)
        // 如果是循环依赖提前暴露的 Bean,这里会打印 trace 日志
        if (this.logger.isTraceEnabled()) {
            if (this.isSingletonCurrentlyInCreation(beanName)) {
                this.logger.trace("Returning eagerly cached instance of singleton bean '" 
                    + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
            } else {
                this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }

        // 3. 如果是 FactoryBean,还需要处理 getObject() 包装
        beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
    }
}

可以发现sharedInstance和beanInstance指向了同一个地址

2025-09-13T12:42:11.png

如果下次进行getBean的时候,并且这个Bean对象设置原型模式,就会新疆一个Bean,否则则直接返回原来的对象
2025-09-13T12:42:16.png

Ioc容器加载过程:
1. new ApplicationContext(配置类/xml);
@SpringBootApplication
@Configurable
public class Main {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Main.class);
        Object bean = applicationContext.getBean("IService");
    }
}

//这里进行new一个ConfigurableApplicationContext
public class SpringApplication {
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return (new SpringApplication(primarySources)).run(args);
    }
}
2. 解析配置--->扫描包--->bean的定义信息读取进来--->BeanDefinition-->map

BeanDefinition = 把类的配置信息登记到 Spring 容器(BeanFactory)的“元数据字典”里,方便后续创建 Bean。

BeanDefinition,里面写着:

  • beanClass = UserService
  • scope = singleton
  • lazyInit = false
3.循环一个个创建bean--->条件判断--->懒加载的bean\多例Bean\抽象bean

条件判断的时候会判断是否是单例Bean,是单例Bean的话进行一个Bean对象的创建,因为

BeanFactory#getBean
2025-09-13T12:42:26.png

4. 通过beanfactory.getBean创建bean对象

创建之前先获取

5.实例化 -> 在ApplicationContext中,通过调用getBean(Spring boot中是通过BeanFactory拿到)拿到BeanDifinitionMap通过name拿到这个Bean的反射射new AAService()
6.DI 属性注入 (@autowrited 属性注入是进行注入一些属性,比如拿到字段上含有@autowrite注解的字段,进行getBean获取到后通过反射设置进去。)
7.0 初始化前 BeanPostProserssor.postProcessBeforeInitialization
7.初始化 (初始化后才算完整的Bean对象,回调一些方法)-> 如果你先调用自己的初始化方法,或者其他回调方法,都是在这里进行调用的,因为没有初始化的时候这个类的属性都没注入进来,自然而然也无法进行调用其他方法。回掉方法可以使用@Construct注解或者implemnt BeanPostProcessor重写里面的回调方法来实现。
7.1初始化后 -->postProcessAfterInitialization--> AOP --> 动态代理对象

2025-09-13T12:42:38.png
查看一下BeanPostProserssorInitialization实现类 AbstractAutoProxyCreator#postProcessAfterInitialization

2025-09-13T12:42:42.png

继续往下看里面的创建代理对象的方法,这个实现原理实际上就是根据AspectJ中的切点表达式,进行匹配,比如这个切点@Pointcut里面的表达式,如果当前的类满足切点表达式的话,则可以执行if中的逻辑,进行执行createProxy创建一个代理对象

@Aspect
@Component
public class LogAspect {

    // 切点表达式:拦截 com.example.service 包下所有类的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceMethods() {}

    // 在方法执行前打印日志
    @Before("serviceMethods()")
    public void beforeServiceMethod() {
        System.out.println("【AOP】调用 Service 方法之前...");
    }
}

2025-09-13T12:42:49.png
继续往下看createProxy这个创建代理对象的源码

2025-09-13T12:42:54.png

继续往下看,看他怎么拿到代理对象的

2025-09-13T12:42:59.png

可以看到他的这个AopProxy代理对象的实现类(我这个项目引用了第三方的maven包没有进行排除,实际上就2个实现类,一个cglib一个jdk的动态代理)

2025-09-13T12:43:06.png

先看一下JDK的动态代理的实现(一目了然,能看到是使用了一个代理类进行创建代理对象的),CGlib也是差不多的就是创建一个代理对象
2025-09-13T12:43:17.png

这里我写一个简单的创建代理对象的例子,了解一下代理对象是什么

public interface UserService {
    void addUser(String name);
}

public class UserServiceImpl implements UserService {
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

public class MyInvocationHandler implements InvocationHandler {
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用前:日志记录");
        Object result = method.invoke(target, args); // 调用真实方法
        System.out.println("调用后:方法执行完成");
        return result;
    }
}

// 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        new MyInvocationHandler(target));

proxy.addUser("Tom");

//输出
调用前:日志记录
添加用户:Tom
调用后:方法执行完成
8.缓存起来 ->放到一级缓存singletonObjects

getBean() --> 动态代理对象 -->调用方法时被增强

只有实现了AOP的获取的才是代理对象

AOP-> 动态代理

Spring动态代理:

JDK。 bean实现了接口使用JDK. 代理对象:动态生成实现目标bean接口的代理对象

CGlib。 没有实现接口使用cglib

JDK
代理对象 implment IAService{
    add(){
        // 前置通知    
    
        //调用你的目标方法
        AService.add()
        //后置通知
    }
}

CGlib
代理对象 extends 目标bean {
    // 重写目标方法
}
QA:
1.一级缓存能不能解决循环依赖

能(如果只有一级缓存,那么锁粒度就很大,要锁整个getBean方法)

2.二级缓存能不能解决循环依赖

能(二级缓存可以降低锁的粒度)

二级缓存 确实可以拿到“半成品 Bean”,解决最基础的循环依赖。虽然spring现在看是三级缓存解决的循环依赖,实际上二级缓存也可以解决,但是用二级缓存解决的话会导致代理对象注入错误(也就是只能返回原始 Bean,如果这个 Bean 需要 AOP 增强,就会导致依赖方注入的不是代理对象,增强逻辑失效)。所以需要三级缓存

3.三级缓存是解决aop创建代理对象问题吗?

(如果循环依赖时,注入了原始对象,后面再生成代理对象,就会出现 注入的不是最终的 Bean 的问题。)

三级缓存这里有个误区,好多人以为三级缓存就是用来解决循环依赖的的,实际上只有一级缓存或者二级缓存都可以解决循环依赖!

三级缓存还是主要用来规范Bean创建的生命周期的,如果没有三极缓存,那么Bean就必须要在实例化的时候创建代理AOP对象,加入三级缓存的时候让其只有在循环依赖的时候才会创建动态代理,保证他的规范性

不全是,

可以看到三级缓存他缓存的是

2025-09-13T12:43:40.png

getSingleton() —— 获取 Bean 时用三级缓存

// 逻辑就是: 一级缓存 → 二级缓存 → 三级缓存(工厂生成)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先从一级缓存取(完全实例化好的 Bean)
    Object singletonObject = this.singletonObjects.get(beanName);

    // 2. 如果没有 && 正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2.1 尝试从二级缓存取(半成品 Bean)
            singletonObject = this.earlySingletonObjects.get(beanName);

            // 2.2 如果还没有 && 允许提前暴露
            if (singletonObject == null && allowEarlyReference) {
                // 2.3 从三级缓存取 ObjectFactory,调用它创建早期引用(可能是代理对象)
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 放进二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 删除三级缓存里的工厂
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

再往下看可以看到一个往3级缓存中put内容的方法,put进去的内容是一个 **ObjectFactory** 工厂
2025-09-13T12:43:50.png

在 AbstractAutowireCapableBeanFactory#doCreateBean 里,调用的DefaultSingletonBeanRegistry#addSingletionFactory方法。

这里可以看到给三级缓存中原来存的是一个匿名函数!

2025-09-13T12:43:56.png

迎刃而解了,RootBeanDefinition看一下可以大概猜到了,存的相当于是告诉你创建哪个类的,这些累的基本信息都有了,直接反射就可以创建

2025-09-13T12:44:11.png

a.三级缓存还可以 支持代理对象(AOP、事务)在循环依赖场景下的正确注入,也就是说让代理对象都是在实例化后创建的

初始化 判断 只有在循环依赖中bean,才在循环依赖中提前创建AOP代理对象,防止aop对象被注入的不是代理对象,也就说是如果一级缓存里没有bean对象,但是二级缓存中没有,这时候要用到三级缓存(实际上三级缓存存的是一个函数式的接口用来存一个bean对象)拿到这个aop Bean对象存到二级缓存中

b.解决循环依赖(提前暴露半成品 Bean)

4.Spirng怎么解决多级缓存循环依赖问题?

⚠️注意:这个和第6个问题不一样!这个是单例Bean,第6题是多例bean

使用三级缓存(就是第三级缓存,而不是三个缓存!)

@Component
class A {
    @Autowired B b;
}

@Component
class B {
    @Autowired A a;
}

注意这里提示了循环依赖报错,需要手动添加配置文件,Spring Boot 2.6.x 开始,默认就关闭了循环依赖的支持,加上配置后就可以了

spring.main.allow-circular-references=true

2025-09-13T12:44:33.png

注意这里有个小提示,

a.如果Bean 被 AOP 代理了,就需要加上@Lazy注解

  • 如果 A 或 B 上有 @Transactional@Async@Validated 之类的注解,Spring 会给它创建代理对象
  • 在循环依赖过程中,Spring 可能没法把“代理对象”提前暴露出去 → 导致失败

b.作用域不是单例 ,比如是圆型模式@Scope("prototype") 那 Spring 就不会缓存半成品对象了 → 无法解决循环依赖

c.如果 A 或 B 不是 Spring 管理的 Bean(比如你自己 new 出来的),Spring 自然没法做循环依赖处理。

排查步骤

a.确认 AB 上是不是 @Component@Service,并且在 Spring 扫描路径里。

b.确认 没加 **@Scope("prototype")**

c.如果用的是 Spring Boot 2.6+,先加上:

spring.main.allow-circular-references=true

d.检查是否加了 @Transactional 或者别的切面注解。若有,先去掉测试,或者在依赖处加 @Lazy

5.Spring 怎么保证单例 Bean 的创建是线程安全的?

DefaultSingletonBeanRegistry 里的缓存机制 + synchronized 锁:

DCL机制(学过单例的都知道这个,双重检查锁!),但是Spring这里也是类似于DCL的

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {  //类似 DCL 的第一次检查
                synchronized(this.singletonObjects) {
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) { //再检查三级缓存和二级缓存,类似 DCL 的第二次检查
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }

        return singletonObject;
    }
6.Spring是否解决多例Bean的循环依赖详解
类型Spring 是否支持循环依赖原因
单例(singleton)✅ 支持Spring 可以通过 二级缓存(earlySingletonObjects) 暴露半成品 Bean,从而解决循环依赖
多例(prototype)❌ 不支持Spring 不会缓存多例 Bean,每次请求都会创建新的实例,无法提前暴露半成品 Bean

为什么多例 Bean 无法解决循环依赖

原理:

多例 Bean 每次调用 **getBean()** 都会创建新的实例

MyBean a = context.getBean(MyBean.class);
MyBean b = context.getBean(MyBean.class); // 新实例
  1. Spring 不把多例 Bean 放入 singleton 缓存

    • 因此没有 earlySingletonObjects 来存半成品 Bean
    • 循环依赖时,A 需要 B,但 B 还没创建完成 → Spring 无法提前返回 B 的引用
  2. 结果:

    • 会抛出 BeanCurrentlyInCreationException
    • 循环依赖失败

Spring 会报错:BeanCurrentlyInCreationException: Error creating bean with name 'A'

因为没有缓存机制来提前暴露半成品 Bean

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

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

解决多例循环依赖的方法

1.改成单例 Bean,Spring 可以用三级缓存机制解决循环依赖

2.使用 @Lazy 延迟注入

3.使用 ObjectFactoryProvider 手动注入

@Autowired
private ObjectProvider<B> bProvider;
...
B b = bProvider.getObject();

4.重新设计 Bean 依赖关系

我觉得这个是最重要的,如果有循环依赖的问题,你要好好想想是不是真的有这个需要,还是说自己的代码设计失误!

7.Spring是否解决构造函数中的循环依赖详解

a.什么是构造函数循环依赖

构造函数循环依赖指的是:两个或多个 Bean 在 构造函数中相互依赖,例如:

  • A 需要 B 构造完成
  • B 需要 A 构造完成
  • 形成“死循环”
@Component
class A {
    private final B b;
    public A(B b) {       // 构造函数依赖 B
        this.b = b;
    }
}

@Component
class B {
    private final A a;
    public B(A a) {       // 构造函数依赖 A
        this.a = a;
    }
}

b.Spring 是否能解决?

单例 Bean 的 setter/字段注入循环依赖可以解决
构造函数循环依赖无法自动解决

原因:

  1. Spring 的循环依赖解决机制依赖“提前暴露半成品 Bean”

    • 二级缓存(**earlySingletonObjects**)存放的是 实例对象
    • 三级缓存(**singletonFactories**)存放的是 ObjectFactory
    • Spring 可以在 属性注入阶段暴露半成品 Bean,供其他 Bean 注入
  2. 构造函数依赖问题

    • 构造函数阶段,Bean 必须先创建实例才能被依赖
    • Spring 无法提前创建半成品 Bean(因为构造函数还没执行完成)
    • 没有实例可暴露,循环依赖就会导致 BeanCurrentlyInCreationException

c.如何解决构造函数循环依赖?

改成 setter/字段注入(非构造函数)

@Component
class A {
    @Autowired
    private B b;
}

@Component
class B {
    @Autowired
    private A a;
}

使用 @Lazy 延迟注入

8.循环依赖被关闭了?

循环依赖被关闭的背景

  • 默认情况下:Spring 通过 二级缓存/三级缓存 解决循环依赖(仅限单例 + setter/字段注入)。
  • 当我们关闭循环依赖(allowCircularReferences=false)后,Spring 不会把“半成品 Bean”提前暴露出来,因此循环依赖就会直接报错。spring.main.allow-circular-references=false
AOP在循环依赖中会有的问题

1.一级缓存 和依赖注入的Bean不一致

循环依赖中AOP动态的代理, 初始化就不创建了

如果循环以来中创建了

2025-09-13T12:44:54.png

初始化时就不会在创建动态代理Bean了

2025-09-13T12:44:58.png

小tips:本文可能受个人技术能力限制,如果有错误地方可以指出或者加我微信,评论区自动过滤垃圾评论