Bean的生命周期

new 一个对象只有一步。Spring 从容器里取出一个 bean,中间要跑十几个回调,从 BeanNameAware 到 @PostConstruct 再到 afterPropertiesSet。

这些回调是留给外部的扩展点。@Autowired 怎么注进去的、AOP 代理在哪一步包上去的、@PostConstruct 被谁调用的,答案都是"某个回调干的"。把这条链路理顺,这些问题就都能答上来了。

整条链路

入口是 AbstractAutowireCapableBeanFactory 的 doCreateBean,主干就在这个方法里。简化一下:

java 复制代码
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 1. 实例化,拿到一个属性全是 null 的原始对象
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();

    // 2. 扫描注解元数据,@Autowired 要注入哪里在这里被缓存
    applyMergedBeanDefinitionPostProcessors(mbd, bean.getClass(), beanName);

    // 3. 提前把 ObjectFactory 放进三级缓存,为循环依赖留后路
    if (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 4. 属性填充(@Autowired 在这一步真正注入)
    populateBean(beanName, mbd, instanceWrapper);

    // 5. 初始化(Aware、@PostConstruct、afterPropertiesSet、AOP)
    Object exposedObject = initializeBean(beanName, bean, mbd);

    // 6. 注册销毁回调
    registerDisposableBeanIfNecessary(beanName, bean, mbd);

    return exposedObject;
}

后面按这六步展开。


实例化

postProcessBeforeInstantiation

createBeanInstance 之前,容器会先问一遍所有 InstantiationAwareBeanPostProcessor:

java 复制代码
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
    return bean;   // 短路,后面全不走了
}

postProcessBeforeInstantiation 如果返回了非 null 的对象,这个方法就把它当成最终 bean 直接返回,实例化、属性填充、初始化全都不走了。这个钩子用得很少,但它是理解"为什么有些对象不受 @PostConstruct 影响"的关键。

用哪个构造器

createBeanInstance 这一步就是反射调构造器,出来的对象字段全是 null。麻烦的地方是用哪个构造器。

Spring 通过 determineCandidateConstructors 来挑,AutowiredAnnotationBeanPostProcessor 里的规则是:

text 复制代码
1. 有标注 @Autowired(required=true) 的构造器  → 用它,而且只能有一个
2. 都没标注,但有且仅有一个有参构造器          → 用它
3. 多个有参构造器且都没标注                  → 用无参构造器;没有无参的就报错

所以只写一个带参构造器时可以不加 @Autowired,Spring 认得出来。写两个以上就必须显式标注,否则它不知道该用哪个。

提前扫描注解

java 复制代码
applyMergedBeanDefinitionPostProcessors(mbd, bean.getClass(), beanName);

@Autowired、@Value、@Resource 要注入哪些字段、哪些方法,在这里被扫出来缓存成 InjectionMetadata。真正注入的时机是后面的 populateBean,这一步只是提前把活干了,避免每次注入都重新反射扫一遍类。


属性填充

populateBean 干的事就是给字段赋上依赖:

java 复制代码
protected void populateBean(String beanName, BeanDefinition mbd, BeanWrapper bw) {
    // 1. 给 InstantiationAwareBeanPostProcessor 一个机会,返回 false 可以跳过整个属性填充
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
            if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                return;
            }
        }
    }

    // 2. @Autowired / @Resource 注入走这里
    for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
        bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
    }

    // 3. XML 里配的 <property> 注入走这里
    applyPropertyValues(beanName, mbd, bw, pvs);
}

注解注入和 XML 注入的区别就在这。注解是 BeanPostProcessor 处理的(AutowiredAnnotationBeanPostProcessor 管 @Autowired/@Value,CommonAnnotationBeanPostProcessor 管 @Resource),XML 的 <property> 是容器自己处理的。

如果这一步里拿不到某个依赖,而这个依赖又反过来依赖当前 bean,就构成循环依赖,会抛 BeanCurrentlyInCreationException。


初始化

initializeBean 是整条链路里回调最密的一段:

java 复制代码
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
    // 1. Aware 回调
    invokeAwareMethods(beanName, bean);

    // 2. BeanPostProcessor 的前置回调(@PostConstruct 就在这一步被调)
    Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);

    // 3. afterPropertiesSet + init-method
    invokeInitMethods(beanName, wrappedBean, mbd);

    // 4. BeanPostProcessor 的后置回调 ← AOP 代理在这生成
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

    return wrappedBean;
}

Aware 回调

Aware 是一组接口,实现它就能拿到容器内部的对象。顺序是固定的:

text 复制代码
BeanNameAware.setBeanName()                → bean 的名字
BeanClassLoaderAware.setBeanClassLoader()  → 类加载器
BeanFactoryAware.setBeanFactory()          → BeanFactory

这三个在 invokeAwareMethods 里直接调。另一组在 ApplicationContextAwareProcessor 里调,它是注册在列表最前面的 BeanPostProcessor:

text 复制代码
EnvironmentAware
EmbeddedValueResolverAware
ResourceLoaderAware
ApplicationEventPublisherAware
MessageSourceAware
ApplicationContextAware.setApplicationContext()   → 拿 ApplicationContext

所以完整顺序是 BeanNameAware → BeanClassLoaderAware → BeanFactoryAware → 后面那一组,最后才是 ApplicationContextAware。

BeanPostProcessor 的两个回调

BeanPostProcessor 是整个生命周期里最有用的扩展点,核心就两个方法:

java 复制代码
public interface BeanPostProcessor {
    default Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; }
    default Object postProcessAfterInitialization(Object bean, String beanName) { return bean; }
}

BeforeInitialization 跑在 @PostConstruct 那个位置附近,容器里现成的实现有 ApplicationContextAwareProcessor(处理上面那组 Aware)和 CommonAnnotationBeanPostProcessor(调用 @PostConstruct)。

AfterInitialization 更关键,AOP 代理就是在这里生成的。AbstractAutoProxyCreator 的 postProcessAfterInitialization 判断这个 bean 需不需要被代理,需要的话返回一个代理对象,把原始对象替换掉:

@Transactional、@Async、@Cacheable 能生效,靠的就是 AbstractAutoProxyCreator 在 postProcessAfterInitialization 里把原始对象换成了代理对象

这里也是"同类内部方法调用 @Transactional 不生效"的原因,调用的入口根本没经过代理。

afterPropertiesSet 和 init-method

invokeInitMethods 里按顺序调两个东西:

java 复制代码
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd) {
    // 1. 实现了 InitializingBean 接口的
    if (bean instanceof InitializingBean) {
        ((InitializingBean) bean).afterPropertiesSet();
    }
    // 2. 配置里指定的 init-method(@Bean(initMethod=...) 或 XML 的 init-method)
    if (mbd != null && StringUtils.hasLength(mbd.getInitMethodName())) {
        invokeCustomInitMethod(beanName, bean, mbd);
    }
}

两个都能做"属性填充完之后"的初始化,选哪个基本看习惯。afterPropertiesSet 是接口实现,方法名固定;init-method 不侵入代码,改不了的第三方类库只能用它。

三者(@PostConstruct、afterPropertiesSet、init-method)的执行顺序是 @PostConstruct → afterPropertiesSet() → init-method,为什么是这个顺序放到后面「几个容易混的点」那节说。


销毁

容器关闭(context.close() 或者 JVM 的关闭钩子)时走销毁。前提是这一步注册过:

java 复制代码
registerDisposableBeanIfNecessary(beanName, bean, mbd);

只有单例、或者实现了销毁方法的 bean 才会被登记。销毁顺序和初始化是对称的:

text 复制代码
@PreDestroy                                  (DestructionAwareBeanPostProcessor 调用)
→ DisposableBean.destroy()
→ destroy-method

原型(prototype)的 bean 容器不管销毁,容器不持有它的引用,用完之后得自己负责释放。这一点经常被忽略。


把顺序跑一遍

把所有回调都实现一遍,输出顺序是这样的:

java 复制代码
@Component
public class LifecycleBean implements BeanNameAware, BeanFactoryAware,
        ApplicationContextAware, InitializingBean, DisposableBean {

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

    @Autowired public void setDep(Dep dep) { System.out.println("2. @Autowired 属性填充"); }

    @Override public void setBeanName(String name) { System.out.println("3. BeanNameAware"); }
    @Override public void setBeanFactory(BeanFactory bf) { System.out.println("4. BeanFactoryAware"); }
    @Override public void setApplicationContext(ApplicationContext ctx) { System.out.println("5. ApplicationContextAware"); }

    @PostConstruct public void postConstruct() { System.out.println("6. @PostConstruct"); }
    @Override public void afterPropertiesSet() { System.out.println("7. afterPropertiesSet"); }
    public void initMethod() { System.out.println("8. init-method"); }

    @PreDestroy public void preDestroy() { System.out.println("9. @PreDestroy"); }
    @Override public void destroy() { System.out.println("10. DisposableBean.destroy"); }
    public void destroyMethod() { System.out.println("11. destroy-method"); }
}
text 复制代码
1. 构造器
2. @Autowired 属性填充
3. BeanNameAware
4. BeanFactoryAware
5. ApplicationContextAware
6. @PostConstruct
7. afterPropertiesSet
8. init-method
--------- 容器关闭 ---------
9. @PreDestroy
10. DisposableBean.destroy
11. destroy-method

BeanPostProcessor 的两个回调分别落在 6 之前和 8 之后。


几个容易混的点

BeanPostProcessor 和 BeanFactoryPostProcessor

名字很像,管的东西完全不一样:

BeanFactoryPostProcessor BeanPostProcessor
作用对象 BeanDefinition(还没实例化的配置) bean 实例
时机 bean 实例化之前 bean 初始化前后
典型实现 ConfigurationClassPostProcessor(解析 @Configuration/@Component) AutowiredAnnotationBeanPostProcessor、AbstractAutoProxyCreator

前者改的是还没实例化的 BeanDefinition,后者改的是已经造出来的 bean 实例。所以在 refresh() 里,BeanFactoryPostProcessor 比 BeanPostProcessor 先执行。

循环依赖和三级缓存

doCreateBean 里那句 addSingletonFactory 就是为循环依赖准备的,位置在 populateBean 之前。

容器里有三个 map:

java 复制代码
singletonObjects        // 一级:完整的单例 bean
earlySingletonObjects   // 二级:实例化了但还没填充属性的半成品
singletonFactories      // 三级:ObjectFactory,用来生成早期引用(可能是代理)

A 依赖 B、B 依赖 A 时:A 实例化完,先把一个 ObjectFactory 放进三级缓存,然后去填充 B;B 需要 A,走 getSingleton,一级二级都没有,从三级缓存拿到 ObjectFactory,调 getObject() 拿到 A 的早期引用,放进二级缓存,把三级缓存里的删掉;B 填充完、初始化完,A 再继续填充 B,然后自己也初始化完,进一级缓存。

这里我们想一个问题,为什么是三级而不是两级?答案是为了 AOP。如果只有两级缓存,往二级里放的只能是 A 的原始对象。但 A 可能需要被代理(比如带 @Transactional),B 注入的就必须是代理对象,不能是原始对象。三级缓存里的 ObjectFactory 把这个决定推迟到"真的有人需要早期引用"的那一刻,getEarlyBeanReference 会在这时候判断要不要提前创建代理。

换句话说,三级缓存省下的不是一次创建,而是"到底要不要提前造代理"这个决定的时机。

AbstractAutoProxyCreator 实现了这个钩子,它把生成的代理记进 earlyProxyReferences,后面 postProcessAfterInitialization 里发现已经代理过了就不再包一层,避免生成两个代理。

两个限制要记住:

  • 只能解决单例 + setter/字段注入。构造器注入解决不了,因为 addSingletonFactory 在构造器跑完之后才执行,构造器阶段还没法把自己暴露出去
  • Spring Boot 2.6 之后默认禁止循环依赖(spring.main.allow-circular-references=false)。这套机制知道就行,正经做法还是把依赖拆开

FactoryBean 和 BeanFactory

BeanFactory 是容器本身;FactoryBean 是一个用来自定义 bean 创建逻辑的 bean,实现 getObject() 就能接管"这个 bean 怎么造"。

从容器里 getBean("x") 拿到的是 getObject() 的产物,想拿 FactoryBean 本身要加前缀:

java 复制代码
context.getBean("myFactoryBean");    // getObject() 的返回值
context.getBean("&myFactoryBean");   // FactoryBean 自己

MyBatis 的 MapperFactoryBean、Spring 的 ProxyFactoryBean 都是这个套路。它的产物也会走一遍 postProcessAfterInitialization,所以 AOP 在 FactoryBean 产出的对象上一样生效。

@PostConstruct、afterPropertiesSet、init-method 的顺序

text 复制代码
@PostConstruct  →  afterPropertiesSet()  →  init-method

@PostConstruct 能排在最前面,是因为它不是容器直接调的,而是由 CommonAnnotationBeanPostProcessor 在 postProcessBeforeInitialization 里调的,这个位置天然在 invokeInitMethods 前面。

afterPropertiesSet 是 invokeInitMethods 里的第一句,init-method 是第二句,这个顺序写死在代码里。

顺带一提,@PostConstruct 由 BeanPostProcessor 触发这件事有个副作用,自定义的、没实现 PriorityOrdered/Ordered 的 BeanPostProcessor,它的 postProcessBeforeInitialization 会排在 @PostConstruct 后面。想让自定义回调在 @PostConstruct 之前动手,得实现 Ordered 把顺序提前。

相关推荐
Canmag 兴隆磁性1 小时前
自动装盒设备 AO-WCMF
学习·永磁材料·充磁
不停喝水1 小时前
【前端转全栈java速通课】 项目实战④-1 Spring Boot 创建项目-定义接口-请求参数处理-分层规范-依赖注入
java·前端·spring boot
坤坤子吖1 小时前
C++11——Lambda、function、bind 与可调用对象
c++·笔记·学习
上下求索,莫负韶华1 小时前
Spring全家桶
java·后端·spring
行百里er1 小时前
Redis 性能优化——内存、慢查询、Big Key 与 Hot Key
redis·后端
优化Henry1 小时前
LTE站点以太网光模块告警处理实践
运维·网络·笔记·学习·信息与通信
w_zero_one1 小时前
链表(2)
java·数据结构·链表
白杨尚青1 小时前
C++入门篇(十一):string(中)——容量与增删改查:一篇吃透所有常用接口(万字详解)
java·开发语言·c++
职豚求职小程序2 小时前
27届携程笔试测评|能力测评+英语测评高分过
笔记