Spring Bean 生命周期与加载顺序:从 new 对象到容器管理 Bean
Spring Bean 生命周期与加载顺序:从 new 对象到容器管理 Bean
面试指北 · 去哪儿
题意
这道题表面在问“Bean 的生命周期”,实际面试官通常想确认三件事:
- 你是否清楚 Spring Bean 从实例化、属性注入、初始化到销毁的完整链路;
- 你是否能区分 Java 里
new一个对象和 Spring 容器管理一个 Bean 的差别; - 当多个 Bean 存在依赖关系时,谁先加载、谁先实例化,构造器注入和字段注入对顺序有什么影响。
换句话说,题目里的“对象 new 和 Bean 加载的顺序”可以拆成两个问题:普通对象创建与 Spring Bean 创建不是一回事;Spring Bean 的加载/实例化顺序主要由依赖关系和作用域决定,而不是简单按代码扫描顺序执行。
结论
先给一个可以直接回答的版本:
new Object()是 Java 语言层面的对象创建,只负责在堆上分配对象并调用构造方法;它不会被 Spring 容器管理,也不会自动完成依赖注入、Aware 回调、BeanPostProcessor增强、AOP 代理、初始化方法和销毁回调。- Spring Bean 的生命周期可以概括为:读取 BeanDefinition → 实例化 → 属性填充/依赖注入 → Aware 回调 → BeanPostProcessor 初始化前处理 → 初始化方法 → BeanPostProcessor 初始化后处理(可能生成代理)→ 使用 → 销毁。
- 默认的单例 Bean 会在容器启动阶段预实例化;原型 Bean 每次
getBean时创建;懒加载 Bean 在首次使用时创建。 - Bean 的实例化顺序主要看依赖关系:A 依赖 B,通常 B 要先准备好。构造器注入的依赖必须在 A 构造之前完成;字段注入和 Setter 注入是在 A 实例化之后填充,因此不能在构造器里直接使用尚未注入的字段。
- 不要依赖类扫描顺序、BeanDefinition 注册顺序或
@Order来控制 Bean 的实例化顺序。@Order主要影响集合注入排序、拦截器/切面顺序等;需要强制先后关系时用@DependsOn或显式依赖。
要点展开
1. Bean 生命周期的主流程
Spring Bean 的完整生命周期可以拆成下面这些阶段。不同作用域、不同配置下有些阶段会省略,但主线基本一致。
| 阶段 | 主要动作 | 常见回调/扩展点 |
|---|---|---|
| BeanDefinition 加载 | 解析 XML、注解、配置类,把 Bean 定义注册到容器 | BeanDefinitionRegistry |
| 实例化前 | 在真正创建对象前给容器一次干预机会 | InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation |
| 实例化 | 通过反射调用构造方法,或调用 @Bean 方法/工厂方法创建对象 |
构造方法、工厂方法 |
| 属性填充 | 注入 @Autowired、@Value、XML 属性等依赖 |
字段注入、Setter 注入 |
| Aware 回调 | 把容器相关资源注入 Bean | BeanNameAware、BeanFactoryAware、ApplicationContextAware 等 |
| 初始化前处理 | 初始化方法执行前的统一扩展 | BeanPostProcessor#postProcessBeforeInitialization |
| 初始化 | 执行 Bean 自己的初始化逻辑 | @PostConstruct → InitializingBean#afterPropertiesSet → 自定义 init-method |
| 初始化后处理 | 初始化完成后的增强,AOP 代理通常在这里产生 | BeanPostProcessor#postProcessAfterInitialization |
| 使用 | Bean 已经就绪,可以被其他 Bean 或客户端获取 | — |
| 销毁 | 容器关闭时清理资源 | @PreDestroy → DisposableBean#destroy → 自定义 destroy-method |
这里要特别注意初始化方法的顺序:@PostConstruct 先执行,然后才是 afterPropertiesSet(),最后才是 init-method。销毁顺序则是:@PreDestroy 先执行,然后 DisposableBean#destroy(),最后 destroy-method。
用简图表示:
创建 Bean 实例
-> 填充属性 / 依赖注入
-> 调用 Aware 方法
-> BeanPostProcessor Before
-> 初始化:@PostConstruct -> afterPropertiesSet -> init-method
-> BeanPostProcessor After(可能生成 AOP 代理)
-> Bean 就绪使用
-> 销毁:@PreDestroy -> destroy -> destroy-method
2. new 对象与 Spring Bean 的区别
new 和 Spring Bean 最大的区别不是“有没有调用构造方法”,而是创建之后由谁负责后续管理。
| 对比项 | new Object() |
Spring Bean |
|---|---|---|
| 创建者 | 开发者代码 | Spring 容器 |
| 依赖注入 | 不会自动发生 | 容器负责注入 |
| Aware 回调 | 没有 | 支持 |
| BeanPostProcessor | 没有 | 支持 |
| AOP 代理 | 没有 | 通常由初始化后处理器生成 |
| 初始化/销毁回调 | 需要自己调用 | 容器按生命周期调用 |
| 作用域 | 由开发者控制 | 单例、原型、请求、会话等 |
| 容器关闭销毁 | 不受容器管理 | 单例 Bean 会被容器销毁 |
所以,如果一个对象是通过 new 创建后自己使用的,它就是一个普通 Java 对象。即使这个类上写了 @Component,只要不是由 Spring 容器创建并管理,它也不会自动获得依赖注入、事务、AOP 等能力。
但要注意一个容易混淆的点:在 @Bean 方法里写 return new ExampleBean(),这个 new 只是“实例化手段”,对象返回给 Spring 后仍然会被容器继续执行属性填充、初始化回调和代理增强。也就是说,是不是 Spring Bean,关键看对象最终是否交给容器管理,而不是看有没有出现 new 关键字。
3. Bean 加载与实例化顺序
Spring 容器启动时大致会经历:解析配置、注册 BeanDefinition、执行 BeanFactoryPostProcessor、注册 BeanPostProcessor、预实例化非懒加载单例。默认情况下,单例 Bean 在容器启动阶段完成创建,而不是等到第一次 getBean 才创建。
影响实例化顺序的核心因素是依赖关系:
- A 依赖 B,通常 B 要先创建或先准备好;
- 构造器注入的依赖必须在 A 构造之前可用,因此构造器依赖的 Bean 会先被解析和创建;
- 字段注入、Setter 注入发生在 A 实例化之后,所以 A 可以先构造出来,再填充 B;
- 如果 A 在构造器里直接使用字段注入的 B,此时 B 还没注入,很容易得到
null; @DependsOn可以强制某个 Bean 先创建,即使二者没有直接注入关系;@Lazy可以让 Bean 延迟到首次使用时再创建;- 原型 Bean 每次获取都会创建新实例,Spring 不负责其销毁回调;
- 单例 Bean 默认由容器管理销毁,容器关闭时才触发销毁流程。
这里要强调:@Order 一般不等于 Bean 实例化顺序。它更多用于控制集合注入时的排序、拦截器链顺序、切面优先级等。真正需要控制初始化先后时,优先使用依赖注入或 @DependsOn,不要依赖扫描顺序这种不稳定因素。
4. 代码演示
下面用一个例子把生命周期串起来。示例中 ExampleBean 由 @Bean 方法返回,因此 new ExampleBean() 只是实例化动作,返回后仍然由 Spring 完成依赖注入和生命周期回调。
public class DependencyBean {
public DependencyBean() {
System.out.println("2. DependencyBean 创建,用于注入");
}
}
public class ExampleBean implements InitializingBean, DisposableBean {
@Autowired
private DependencyBean dependency;
public ExampleBean() {
System.out.println("1. Bean 实例化:调用构造方法");
}
@PostConstruct
public void postConstruct() {
System.out.println("3. @PostConstruct 初始化,依赖是否已注入:"
+ (dependency != null));
}
@Override
public void afterPropertiesSet() {
System.out.println("4. InitializingBean.afterPropertiesSet");
}
public void customInit() {
System.out.println("5. 自定义 init-method");
}
@PreDestroy
public void preDestroy() {
System.out.println("6. @PreDestroy 销毁");
}
@Override
public void destroy() {
System.out.println("7. DisposableBean.destroy");
}
public void customDestroy() {
System.out.println("8. 自定义 destroy-method");
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public ExampleBean exampleBean() {
return new ExampleBean();
}
@Bean
public DependencyBean dependencyBean() {
return new DependencyBean();
}
}
启动并关闭容器:
public class Main {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class)) {
System.out.println("Bean 准备使用");
context.getBean(ExampleBean.class);
}
}
}
输出顺序大致如下:
1. Bean 实例化:调用构造方法
2. DependencyBean 创建,用于注入
3. @PostConstruct 初始化,依赖是否已注入:true
4. InitializingBean.afterPropertiesSet
5. 自定义 init-method
Bean 准备使用
6. @PreDestroy 销毁
7. DisposableBean.destroy
8. 自定义 destroy-method
这个例子能说明几个关键点:
- 构造方法执行时,字段注入还没有发生;
@PostConstruct执行时,依赖已经注入完成,适合做依赖就绪后的初始化;afterPropertiesSet在@PostConstruct之后执行;- 自定义
init-method在afterPropertiesSet之后执行; - 销毁阶段顺序是
@PreDestroy→DisposableBean.destroy→ 自定义destroy-method; @Bean方法里的new并不会绕过 Spring 生命周期。
易错点与对比
-
构造器里使用
@Autowired字段,可能是 null
字段注入发生在实例化之后。构造方法执行时,字段还没有被注入,所以不要在构造器里直接使用字段注入的依赖。需要构造时就必须有依赖,应使用构造器注入。 -
@PostConstruct、afterPropertiesSet、init-method不是同一个东西
三者都会执行,但顺序固定:@PostConstruct最先,afterPropertiesSet其次,init-method最后。不要以为写了其中一个,其他就不会执行。 -
销毁回调不是所有 Bean 都会触发
容器关闭时主要销毁由容器管理的单例 Bean。原型 Bean 每次创建新实例,Spring 不负责调用它的销毁方法。手动new出来的对象更不会自动销毁回调。 -
@Order不能解决 Bean 初始化顺序
@Order常用于集合注入排序、AOP 切面顺序、拦截器顺序等,不要用它来控制“哪个 Bean 先创建”。需要先后关系时,用依赖注入或@DependsOn。 -
单例 Bean 默认不是懒加载
默认单例会在容器启动阶段预实例化;加@Lazy后才延迟到首次获取时创建。原型 Bean 则是每次获取都创建。 -
循环依赖要分情况
单例的字段注入/Setter 注入循环依赖,Spring 可以通过提前暴露对象等方式处理;构造器循环依赖通常无法直接解决,会抛出异常。实际开发中更推荐通过设计拆分依赖,而不是依赖循环依赖机制。 -
AOP 代理通常发生在初始化后
BeanPostProcessor#postProcessAfterInitialization是生成代理的常见时机。因此你注入到其他 Bean 里的可能不是原始对象,而是代理对象。普通new出来的对象不会有这层代理。
小结
Spring Bean 的生命周期可以记成一条主线:实例化 → 属性填充 → Aware 回调 → 初始化前处理 → 初始化方法 → 初始化后处理 → 使用 → 销毁。初始化方法顺序是 @PostConstruct、afterPropertiesSet、init-method;销毁顺序是 @PreDestroy、destroy、destroy-method。
new 只是创建一个普通 Java 对象,而 Spring Bean 是由容器创建并托管的对象,额外拥有依赖注入、生命周期回调、BeanPostProcessor 扩展和 AOP 代理等能力。Bean 的加载顺序主要由作用域和依赖关系决定:单例默认启动时预实例化,原型按需创建,懒加载首次使用创建;有依赖时依赖方先准备好,构造器注入要求最严格,字段注入和 Setter 注入发生在实例化之后。实际回答问题或写代码时,不要把“扫描顺序”“@Order”当成控制 Bean 创建顺序的手段。