Bean 作用域与生命周期回调
引言
默认情况下容器里每个 Bean 只有一份实例(singleton),但在“每次请求一个实例”“每个 HTTP 会话一份”等场景就需要其他作用域。同时,Bean 从创建到销毁要经过一系列回调点,理解顺序才能安全地在初始化/销毁时干活。本章覆盖内置作用域与生命周期回调。
核心概念
- singleton(默认):整个容器共享同一实例,容器负责其完整生命周期。
- prototype:每次
getBean或注入时都新建实例;容器只负责“创建+初始化”,不管理其销毁。 - web 作用域:
request(每次 HTTP 请求一个实例)、session(每个会话一个)、application(ServletContext 级),仅在 web 容器环境(如 Spring MVC)可用。 - 生命周期回调(简化顺序):实例化 → 属性注入 →
@PostConstruct→InitializingBean.afterPropertiesSet→ 自定义init-method/@Bean(initMethod=...)→ 就绪可用 → 容器关闭时执行@PreDestroy→DisposableBean.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 环境。生命周期顺序是“构造 → 注入 → 初始化回调 → 使用 → 销毁回调”,初始化/清理逻辑放到对应回调里,而不是塞进业务方法。