" />
文章

Spring Bean 生命周期与加载顺序:从 new 对象到容器管理 Bean

Spring Bean 生命周期与加载顺序:从 new 对象到容器管理 Bean

面试指北 · 去哪儿

题意

这道题表面在问“Bean 的生命周期”,实际面试官通常想确认三件事:

  1. 你是否清楚 Spring Bean 从实例化、属性注入、初始化到销毁的完整链路;
  2. 你是否能区分 Java 里 new 一个对象和 Spring 容器管理一个 Bean 的差别;
  3. 当多个 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 生命周期。

易错点与对比

  1. 构造器里使用 @Autowired 字段,可能是 null
    字段注入发生在实例化之后。构造方法执行时,字段还没有被注入,所以不要在构造器里直接使用字段注入的依赖。需要构造时就必须有依赖,应使用构造器注入。

  2. @PostConstruct、afterPropertiesSet、init-method 不是同一个东西
    三者都会执行,但顺序固定:@PostConstruct 最先,afterPropertiesSet 其次,init-method 最后。不要以为写了其中一个,其他就不会执行。

  3. 销毁回调不是所有 Bean 都会触发
    容器关闭时主要销毁由容器管理的单例 Bean。原型 Bean 每次创建新实例,Spring 不负责调用它的销毁方法。手动 new 出来的对象更不会自动销毁回调。

  4. @Order 不能解决 Bean 初始化顺序
    @Order 常用于集合注入排序、AOP 切面顺序、拦截器顺序等,不要用它来控制“哪个 Bean 先创建”。需要先后关系时,用依赖注入或 @DependsOn。

  5. 单例 Bean 默认不是懒加载
    默认单例会在容器启动阶段预实例化;加 @Lazy 后才延迟到首次获取时创建。原型 Bean 则是每次获取都创建。

  6. 循环依赖要分情况
    单例的字段注入/Setter 注入循环依赖,Spring 可以通过提前暴露对象等方式处理;构造器循环依赖通常无法直接解决,会抛出异常。实际开发中更推荐通过设计拆分依赖,而不是依赖循环依赖机制。

  7. 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 创建顺序的手段。

License:  CC BY 4.0