Bean 作用域与生命周期回调

引言

默认情况下容器里每个 Bean 只有一份实例(singleton),但在“每次请求一个实例”“每个 HTTP 会话一份”等场景就需要其他作用域。同时,Bean 从创建到销毁要经过一系列回调点,理解顺序才能安全地在初始化/销毁时干活。本章覆盖内置作用域与生命周期回调。

核心概念

  • singleton(默认):整个容器共享同一实例,容器负责其完整生命周期。
  • prototype:每次 getBean 或注入时都新建实例;容器只负责“创建+初始化”,不管理其销毁。
  • web 作用域request(每次 HTTP 请求一个实例)、session(每个会话一个)、application(ServletContext 级),仅在 web 容器环境(如 Spring MVC)可用。
  • 生命周期回调(简化顺序):实例化 → 属性注入 → @PostConstructInitializingBean.afterPropertiesSet → 自定义 init-method/@Bean(initMethod=...) → 就绪可用 → 容器关闭时执行 @PreDestroyDisposableBean.destroy → 自定义 destroy-method
  • 三类回调写法等价(注解/Jakarta 标准/接口/自定义方法),官方推荐注解或自定义方法,避免业务类实现框架接口。
  • 注册钩子:context.registerShutdownHook() 让 JVM 退出前触发销毁回调。

代码示例

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 每次获取都是新实例
public class Task {
    private final String id = java.util.UUID.randomUUID().toString();

    @PostConstruct
    void init() { System.out.println("Task 初始化: " + id); }

    @PreDestroy
    void clean() { System.out.println("Task 销毁: " + id); } // prototype 下不会自动执行
}

@Component
public class SingletonHolder {
    private final ObjectProvider<Task> taskProvider; // 每次取都是新的
    public SingletonHolder(ObjectProvider<Task> taskProvider) {
        this.taskProvider = taskProvider;
    }
    public Task nextTask() { return taskProvider.getObject(); }
}

注意点

  • 若在 singleton 里直接注入 prototype Bean,注入的其实是那“一个”固定实例,后续不再变化——需要“每次调用都拿新实例”请用 ObjectProvider/@Lookup/scoped proxy。
  • request/session 作用域在非 web 容器(如普通 main 方法)里访问会抛异常;测试需借助 Mock 的 web 环境。
  • prototype 的 @PreDestroy 不会由容器调用(容器不追踪其销毁),资源清理要自己负责。
  • @PostConstruct/@PreDestroy 在 6.x 中是 jakarta.annotation.*,记得配套 jakarta.annotation-api 依赖。
  • 初始化回调抛异常会让该 Bean 创建失败,容器启动即报错——这是好事,别 try-catch 吞掉。

小结

作用域决定“一份还是多份”:默认 singleton 管全生命周期,prototype 只负责发出去,request/session 依赖 web 环境。生命周期顺序是“构造 → 注入 → 初始化回调 → 使用 → 销毁回调”,初始化/清理逻辑放到对应回调里,而不是塞进业务方法。

笔记加载中…