一个 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,getBean 第一步就是查这张表。单例最终存在 singletonObjects(一级缓存),这也是下一篇要展开的地方。
验证方法
跑通上面那段代码,看到 1 到 7 顺序打印,说明生命周期链路清楚了。再改两处做对照:把 @Bean 改成 @Scope("prototype"),你会发现 destroy 不再打印,因为原型 Bean 的销毁 Spring 不接管;把 BeanPostProcessor 的 postProcessAfterInitialization 改成返回一个包装对象,观察注入方拿到的是不是包装后的实例,能验证"后置处理器返回值替换 Bean"这条规则。
想看得更细,在 doCreateBean 打断点,单步跟一遍 populateBean,看它怎么递归触发依赖 Bean 的创建。
面试速答
- Bean 生命周期主干:实例化 → 属性填充 → 初始化(Aware → beforeInit → afterPropertiesSet → init-method → afterInit)→ 使用 → 销毁。
- 实例化和初始化拆开,是为了中间留出提前暴露的窗口,这是循环依赖能解的前提。
BeanPostProcessor的 afterInit 返回值会替换 Bean,AOP 代理在这里生成。- 原型 Bean 的销毁 Spring 不接管,destroy 不会回调。
- 扩展点顺序记牢:Aware 最早,init-method 在 afterPropertiesSet 之后。
核心收获:Bean 生命周期不是流水账,实例化和初始化之间的那个窗口才是理解循环依赖的钥匙。下一步,去把三级缓存里每一级存的是什么、为什么需要三级而不是两级搞清楚。
本文关键词:BeanDefinition、Bean 生命周期、BeanPostProcessor、初始化方法、单例缓存