← 返回博客
2026-10-15 08:00:01

一个 Bean 从定义到销毁到底走了多少步

一个 Bean 从定义到销毁到底走了多少步

线上有个订单服务,启动时报 BeanCurrentlyInCreationException,堆栈指向两个 Service 互相注入。你想修,但打开 AbstractAutowireCapableBeanFactory 一看,几千行,doCreateBean 里又是 populateBean 又是 initializeBean,根本不知道从哪下手。这篇先把 Bean 从定义到销毁的完整链路走一遍,下一篇再专门拆循环依赖。

手把手实操:让 Bean 的生命周期"说话"

先建一个最小工程,把每个扩展点都打上日志,跑一遍你就知道顺序了。

public class DemoBean implements BeanNameAware, InitializingBean, DisposableBean {
    private String name;

    public DemoBean() { System.out.println("1. 构造器"); }

    @Override
    public void setBeanName(String name) {
        System.out.println("2. BeanNameAware.setBeanName -> " + name);
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println("4. InitializingBean.afterPropertiesSet");
    }

    public void customInit() {
        System.out.println("5. init-method");
    }

    @Override
    public void destroy() {
        System.out.println("7. DisposableBean.destroy");
    }
}

再配一个 BeanPostProcessor:

@Component
public class LogPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("3. beforeInit -> " + beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("6. afterInit -> " + beanName);
        return bean;
    }
}

配置类里用 @Bean(initMethod = "customInit", destroyMethod = "destroy") 注册 DemoBean,启动后关闭容器(context.close()),你会看到 1 到 7 依次打印。

关键点在两个地方容易踩坑。一是 BeanPostProcessor 本身也是 Bean,但它的注册时机早于普通 Bean,所以别指望在 BeanPostProcessor 里注入普通 Bean 还能正常工作,容易拿到未初始化完的对象。二是 destroy 只在容器正常关闭时触发,System.exit 或 kill -9 不会走,生产上做资源释放别只依赖它。

看源码及解析:doCreateBean 的骨架

入口在 AbstractApplicationContext.refresh(),核心是 finishBeanFactoryInitialization,它调用 DefaultListableBeanFactory.preInstantiateSingletons(),最终落到 AbstractAutowireCapableBeanFactory.doCreateBean。这个方法就是生命周期的主干:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    BeanWrapper instanceWrapper = null;
    // 1. 实例化
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    // 2. 提前暴露,解决循环依赖的关键
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
            && isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // 3. 属性填充
    populateBean(beanName, mbd, instanceWrapper);
    // 4. 初始化
    Object exposedObject = initializeBean(beanName, exposedObject, mbd);
    return exposedObject;
}

createBeanInstance 里选构造器:有 @Autowired 构造器优先,多个候选会抛异常;没有就走无参构造。populateBean 负责 @Autowired、@Value、@Resource 的注入,这一步会触发依赖 Bean 的 getBean,循环依赖就发生在这里。

initializeBean 是扩展点的集中地,顺序是:Aware 接口回调 → BeanPostProcessor.postProcessBeforeInitialization → afterPropertiesSet → init-method → postProcessAfterInitialization。注意 postProcessAfterInitialization 的返回值会替换原 Bean,AOP 代理就是在这里生成的,AbstractAutoProxyCreator 就是靠它把原始对象包成代理。

为什么这么设计?Spring 把"实例化"和"初始化"拆开,就是为了在两者之间留出 addSingletonFactory 这个窗口。没有这个窗口,循环依赖无解。代价是容器里可能短暂存在一个"半成品"Bean,所以 getBean 拿到的不一定是完全初始化好的对象——这是设计上的取舍,不是 bug。

BeanDefinition 是这一切的元数据来源,它记录了 scope、lazyInit、initMethodName、dependsOn 等。DefaultListableBeanFactory 持有 Map beanDefinitionMap,getBean 第一步就是查这张表。单例最终存在 singletonObjects(一级缓存),这也是下一篇要展开的地方。

验证方法

跑通上面那段代码,看到 1 到 7 顺序打印,说明生命周期链路清楚了。再改两处做对照:把 @Bean 改成 @Scope("prototype"),你会发现 destroy 不再打印,因为原型 Bean 的销毁 Spring 不接管;把 BeanPostProcessor 的 postProcessAfterInitialization 改成返回一个包装对象,观察注入方拿到的是不是包装后的实例,能验证"后置处理器返回值替换 Bean"这条规则。

想看得更细,在 doCreateBean 打断点,单步跟一遍 populateBean,看它怎么递归触发依赖 Bean 的创建。

面试速答

核心收获:Bean 生命周期不是流水账,实例化和初始化之间的那个窗口才是理解循环依赖的钥匙。下一步,去把三级缓存里每一级存的是什么、为什么需要三级而不是两级搞清楚。

本文关键词:BeanDefinition、Bean 生命周期、BeanPostProcessor、初始化方法、单例缓存