一、Spring Bean 生命周期整体概览
Spring Bean 的生命周期是一个从 "创建" 到 "销毁" 的完整过程,主要由 Spring IoC 容器负责管理。
核心流程:
-
实例化(Instantiation)
- 容器通过反射机制调用Bean的构造函数(默认无参构造或通过@Autowired指定的构造方法)
- 此时Bean对象已经被创建,但属性值均为null或默认值
- 示例:对于UserService类,会先执行
new UserService()操作
-
属性注入(Population)
- 通过Setter方法(@Autowired标注)或字段直接注入(@Resource)完成依赖注入
- 处理@Value注解的配置值注入
- 解析并注入其他Bean的引用(如将UserDao注入到UserService中)
- 此阶段可能出现循环依赖问题,Spring通过三级缓存机制解决
-
初始化(Initialization)
- 执行Aware接口回调(BeanNameAware、BeanFactoryAware等)
- 执行BeanPostProcessor的前置处理(postProcessBeforeInitialization)
- 调用InitializingBean的afterPropertiesSet()方法
- 执行自定义init方法(@PostConstruct或XML配置的init-method)
- 执行BeanPostProcessor的后置处理(postProcessAfterInitialization)
- 典型应用场景:数据库连接池在此阶段完成初始化
-
销毁(Destruction)
- 容器关闭时触发(如调用ConfigurableApplicationContext.close())
- 调用DisposableBean的destroy()方法
- 执行自定义destroy方法(@PreDestroy或XML配置的destroy-method)
- 典型应用:释放数据库连接、关闭文件流等资源清理
完整生命周期:
-
容器启动
- 扫描@Component等注解或解析XML配置
- 生成BeanDefinition元数据
-
实例化阶段
// 示例:通过构造器实例化 public class ExampleBean { public ExampleBean() { System.out.println("构造函数执行"); } } -
属性注入阶段
- @Autowired自动装配
- @Value("${property}")属性注入
- 处理@Resource等注解
-
初始化阶段的关键步骤:
-
Aware接口回调(顺序):
- BeanNameAware.setBeanName()
- BeanClassLoaderAware.setBeanClassLoader()
- BeanFactoryAware.setBeanFactory()
- EnvironmentAware.setEnvironment()
- ApplicationContextAware.setApplicationContext()
-
初始化回调(顺序):
- @PostConstruct方法
- InitializingBean.afterPropertiesSet()
- XML init-method
-
-
销毁阶段示例:
@PreDestroy public void cleanup() { // 释放资源 dataSource.close(); } -
扩展机制:
- BeanPostProcessor:可干预所有Bean的初始化过程
- InstantiationAwareBeanPostProcessor:可干预实例化过程
- DestructionAwareBeanPostProcessor:可干预销毁过程
二、Spring Bean 生命周期各阶段详细解析
1. 阶段 1:Bean 定义的加载与解析(详细扩展)
在 Spring IoC 容器启动时,Bean 定义的加载与解析是首要步骤,具体过程如下:
1.1 定义来源的扩展说明
- XML 配置文件:传统方式,通过
<beans>根标签和嵌套的<bean>标签声明,支持属性注入(<property>)、构造函数注入(<constructor-arg>)等配置。示例:<beans> <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> </bean> </beans> - 注解扫描:通过
@ComponentScan开启组件扫描,结合@Component及其派生注解(如@Service)自动注册 Bean。支持@Autowired实现依赖注入。 - Java 配置类:使用
@Configuration标记配置类,通过@Bean方法显式定义 Bean。适用于需要编程式初始化的场景,如第三方库集成:@Configuration public class AppConfig { @Bean public DataSource dataSource() { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:mysql://localhost:3306/test"); return ds; } }
1.2 BeanDefinition 的深层解析
- 属性存储:
BeanDefinition存储了类名(className)、作用域(scope)、延迟初始化(lazyInit)等元数据。对于 XML 配置,BeanDefinitionReader会将<bean>标签解析为GenericBeanDefinition。 - 合并过程:若存在父子 Bean 定义(通过
parent属性指定),子定义会继承父定义的属性,最终生成RootBeanDefinition。 - 注册时机:
BeanDefinition通常在容器启动时(如ApplicationContext.refresh())被加载到DefaultListableBeanFactory的beanDefinitionMap中。
2. 阶段 2:实例化(Instantiation)(扩展细节)
2.1 实例化策略
- 反射调用:默认通过
Class.newInstance()或构造函数反射(Constructor.newInstance())创建实例。若类无公开无参构造器,需在配置中指定参数,如:<bean id="userService" class="com.example.UserService"> <constructor-arg ref="userRepository"/> </bean> - 工厂方法:支持静态工厂(
factory-method)和实例工厂(factory-bean)创建对象。例如创建单例工具类:public class Utils { private static final Utils INSTANCE = new Utils(); public static Utils getInstance() { return INSTANCE; } }<bean id="utils" class="com.example.Utils" factory-method="getInstance"/>
2.2 循环依赖的实例化处理
- 三级缓存机制:Spring 通过
singletonFactories、earlySingletonObjects、singletonObjects三级缓存解决单例 Bean 的循环依赖。在实例化后、注入属性前,会将未完成的 Bean 存入缓存供其他 Bean 引用。
3. 阶段 3:属性注入(Population)(扩展示例)
3.1 注入方式对比
| 方式 | 配置示例 | 适用场景 |
|---|---|---|
| Setter 注入 | <property name="dao" ref="userDao"/> | 可选依赖或需动态变更的属性 |
| 构造器注入 | <constructor-arg ref="userDao"/> | 强制依赖或不可变对象 |
| 注解字段注入 | @Autowired private UserDao dao; | 代码简洁,常用于现代 Spring |
3.2 复杂类型注入
- 集合注入:支持
<list>、<map>等标签配置集合属性:<bean id="complexBean" class="com.example.ComplexBean"> <property name="names"> <list> <value>Alice</value> <value>Bob</value> </list> </property> </bean> - SpEL 表达式:通过
#{...}实现动态值注入,如:<property name="timeout" value="#{systemProperties['default.timeout'] ?: 30}"/>
4. 阶段 4:Aware 接口回调(扩展接口列表)
4.1 完整 Aware 接口体系
| 接口 | 回调方法 | 典型应用场景 |
|---|---|---|
EmbeddedValueResolverAware | setEmbeddedValueResolver() | 解析 ${...} 占位符 |
MessageSourceAware | setMessageSource() | 国际化消息处理 |
ApplicationEventPublisherAware | setApplicationEventPublisher() | 发布应用事件 |
4.2 实现原理
通过 ApplicationContextAwareProcessor 检测并调用 Aware 接口,该处理器在 AbstractApplicationContext.prepareBeanFactory() 中被注册。
5. 阶段 5:BeanPostProcessor 前置处理(扩展应用)
5.1 典型实现类
- CommonAnnotationBeanPostProcessor:处理
@PostConstruct、@PreDestroy等 JSR-250 注解。 - AutowiredAnnotationBeanPostProcessor:处理
@Autowired和@Value注入。 - AbstractAdvisingBeanPostProcessor:AOP 代理的基础实现类。
5.2 自定义处理器示例
实现属性加密解密:
public class EncryptProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String name) {
if (bean instanceof SensitiveData) {
((SensitiveData) bean).decryptFields();
}
return bean;
}
}
6. 阶段 6:初始化(Initialization)——Bean 功能完善
初始化阶段是对 Bean 实例进行功能完善的关键环节,主要包括以下两个步骤(按顺序执行):
(1)执行 InitializingBean 接口的 afterPropertiesSet() 方法
若 Bean 实现了 InitializingBean 接口,Spring 会调用其 afterPropertiesSet() 方法。该方法的作用是在 Bean 的属性全部注入完成后,执行一些初始化逻辑(如初始化连接池、加载配置文件等)。这个阶段主要用于:
- 验证依赖注入是否完整
- 初始化资源(如数据库连接池)
- 加载配置文件
- 执行其他必须在属性注入完成后才能进行的操作
示例:让 UserService 实现 InitializingBean
public class UserService implements BeanNameAware, ApplicationContextAware, InitializingBean {
private UserDao userDao;
// ... 其他属性和方法
// 实现InitializingBean接口的方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("UserService:InitializingBean回调(afterPropertiesSet())——属性注入完成后执行");
// 此处可编写初始化逻辑,如校验userDao是否注入成功
if (userDao == null) {
throw new RuntimeException("UserDao未注入!");
}
// 初始化缓存
initCache();
// 加载配置文件
loadConfig();
}
private void initCache() {
// 初始化缓存逻辑
}
private void loadConfig() {
// 加载配置文件逻辑
}
}
(2)执行自定义初始化方法(init-method)
除了实现 InitializingBean 接口,我们还可以通过配置指定自定义的初始化方法(如 XML 中的 init-method 属性、注解 @PostConstruct)。Spring 会在 afterPropertiesSet() 方法执行完成后,调用该自定义初始化方法。
需要注意的要点:
- 方法签名要求:自定义初始化方法必须是无参数、无返回值,且不能抛出 checked 异常的方法
- 执行顺序:
- 若同时实现 InitializingBean 和配置 init-method,则 afterPropertiesSet() 先执行,自定义初始化方法后执行
- JSR-250 规范中的 @PostConstruct 注解,其作用与 init-method 一致,且优先级高于 init-method(Spring 会先执行 @PostConstruct 标注的方法,再执行 init-method)
- 配置方式:
- XML 配置:
<bean id="userService" class="com.example.UserService" init-method="init"/> - Java 配置:
@Bean(initMethod = "init") - 注解配置:
@PostConstruct
- XML 配置:
示例:通过 @PostConstruct 指定自定义初始化方法
public class UserService {
// ... 其他属性和方法
@PostConstruct
public void postConstructInit() {
System.out.println("UserService:@PostConstruct注解初始化方法");
// 初始化日志系统
initLogger();
}
public void init() {
System.out.println("UserService:自定义初始化方法(init-method)");
// 初始化统计数据
initStatistics();
}
private void initLogger() {
// 日志系统初始化逻辑
}
private void initStatistics() {
// 统计数据初始化逻辑
}
}
7. 阶段 7:BeanPostProcessor 后置处理 —— 初始化后的增强
初始化阶段完成后,Spring 会再次遍历所有的 BeanPostProcessor 实例,调用其 postProcessAfterInitialization(Object bean, String beanName) 方法,对 Bean 实例进行初始化后的增强处理。
关键特性:
- AOP 实现的核心:这一阶段是 Spring 实现 AOP 的核心环节 —— Spring 会在此处为需要代理的 Bean 生成代理对象(如基于 JDK 动态代理或 CGLIB 代理),并将代理对象返回给容器。后续容器中存储和使用的,就是这个代理对象。
- 处理顺序:在所有初始化回调(InitializingBean.afterPropertiesSet、@PostConstruct、init-method)完成后执行
- 应用场景:
- AOP 代理创建
- 对象包装(如返回装饰器模式实现的包装对象)
- 性能监控代理
- 缓存代理
示例:
public class AopProxyPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 检查是否需要为这个bean创建代理
if (needProxy(bean)) {
// 创建代理对象
return createProxy(bean);
}
return bean;
}
private boolean needProxy(Object bean) {
// 判断是否需要代理的逻辑
return bean.getClass().isAnnotationPresent(Transactional.class);
}
private Object createProxy(Object target) {
// 创建代理的逻辑
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 添加事务处理逻辑
System.out.println("开启事务...");
Object result = method.invoke(target, args);
System.out.println("提交事务...");
return result;
}
});
}
}
例如,若 UserService 需要被 AOP 代理(如添加事务切面),则 postProcessAfterInitialization() 方法会返回 UserService 的代理对象,而非原实例。
8. 阶段 8:Bean 就绪 —— 可被应用程序使用
经过上述所有阶段后,Bean 实例已完全初始化完成,具备了完整的功能,此时会被存储在 Spring IoC 容器中,处于"就绪"状态。
获取和使用 Bean 的方式:
-
显式获取:
- 调用
ApplicationContext.getBean()或BeanFactory.getBean()方法 - 通过
ConfigurableApplicationContext获取
- 调用
-
依赖注入:
- 通过 @Autowired 自动装配
- 通过 @Resource 按名称装配
- 通过 @Inject (JSR-330) 注解注入
- XML 配置中的
<property>或<constructor-arg>元素
示例:在 Controller 中注入 UserService 并使用
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
// 使用就绪的UserService实例
User user = userService.getUserById(id);
if (user != null) {
return ResponseEntity.ok(user);
}
return ResponseEntity.notFound().build();
}
@PostMapping
public ResponseEntity<Void> createUser(@RequestBody User user) {
// 使用就绪的UserService实例
userService.createUser(user);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
Bean 就绪后的状态特征:
- 所有属性已正确注入
- 所有初始化回调已执行完毕
- 所有后置处理已完成
- 如果是需要代理的 Bean,已经是代理对象而非原始对象
- 已经注册到容器的单例缓存中(如果是单例作用域)
阶段 9:销毁(Destruction)—— 释放资源
当 Spring IoC 容器关闭时(如 Web 应用停止、调用 ApplicationContext.close() 方法),会对容器中的 Bean 进行销毁处理,释放资源。
(1)执行 DisposableBean 接口的 destroy() 方法
若 Bean 实现了 DisposableBean 接口,Spring 会调用其 destroy() 方法,执行销毁逻辑(如关闭数据库连接、释放线程池、删除临时文件等)。
典型应用场景:
- 关闭数据库连接池
- 释放网络连接
- 清理线程池
- 删除临时文件
- 持久化缓存数据
- 注销监听器
(2)执行自定义销毁方法(destroy-method)
与初始化阶段类似,我们也可以通过配置指定自定义的销毁方法(如 XML 中的 destroy-method 属性、注解 @PreDestroy)。Spring 会在 destroy() 方法执行完成后,调用该自定义销毁方法。
需要注意的要点:
- 方法签名要求:自定义销毁方法必须是无参数、无返回值,且不能抛出 checked 异常的方法
- 执行顺序:
- 若同时实现 DisposableBean 和配置 destroy-method,则 destroy() 先执行,自定义销毁方法后执行
- JSR-250 规范中的 @PreDestroy 注解,其作用与 destroy-method 一致,且优先级高于 destroy-method(Spring 会先执行 @PreDestroy 标注的方法,再执行 destroy-method)
- 作用域限制:
- 仅单例 Bean(默认作用域)会被 Spring 容器管理销毁过程
- 原型 Bean(scope="prototype")的销毁由开发者手动管理,Spring 容器不会调用其销毁方法
- 配置方式:
- XML 配置:
<bean id="userService" class="com.example.UserService" destroy-method="cleanup"/> - Java 配置:
@Bean(destroyMethod = "cleanup") - 注解配置:
@PreDestroy
- XML 配置:
示例:让 UserService 实现 DisposableBean 并使用 @PreDestroy
public class UserService implements InitializingBean, DisposableBean {
private ExecutorService threadPool;
private Connection connection;
// ... 其他属性和方法
@PreDestroy
public void preDestroy() {
System.out.println("UserService:@PreDestroy注解销毁方法");
// 清理缓存
clearCache();
}
// 实现DisposableBean接口的方法
@Override
public void destroy() throws Exception {
System.out.println("UserService:DisposableBean回调(destroy())");
// 关闭线程池
if (threadPool != null) {
threadPool.shutdown();
}
// 关闭数据库连接
if (connection != null) {
connection.close();
}
}
public void destroyMethod() {
System.out.println("UserService:自定义销毁方法(destroy-method)");
// 其他清理工作
cleanTemporaryFiles();
}
private void clearCache() {
// 清理缓存逻辑
}
private void cleanTemporaryFiles() {
// 清理临时文件逻辑
}
}
容器关闭的触发方式:
- 对于 Web 应用,容器会在应用停止时自动关闭
- 对于独立应用,可以通过以下方式关闭:
- 调用
ConfigurableApplicationContext.close() - 注册 JVM 关闭钩子:
context.registerShutdownHook()
- 调用
- 在测试环境中,通常使用 try-with-resources 或 @DirtiesContext 注解
销毁阶段的最佳实践:
- 确保所有资源都被正确释放
- 避免在销毁方法中抛出异常,这可能导致其他 Bean 的销毁方法不被执行
- 对于原型作用域的 Bean,考虑实现自定义的生命周期管理接口
- 对于需要顺序销毁的 Bean,可以考虑实现 SmartLifecycle 接口来控制销毁顺序
三、Spring Bean 生命周期执行顺序验证
1. 测试代码实现
public class BeanLifecycleTest {
public static void main(String[] args) {
// 1. 启动Spring容器(使用ClassPathXmlApplicationContext加载XML配置)
// 注意:这里使用ClassPathXmlApplicationContext而非ApplicationContext接口,
// 因为需要调用close()方法显式关闭容器
System.out.println("---------------------开始启动Spring容器---------------------");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 分隔线,标记容器启动完成
System.out.println("---------------------Spring容器启动完成,Bean就绪---------------------");
// 2. 获取并使用UserService Bean
// 注意:这里使用getBean()方法获取Bean实例,触发Bean的延迟初始化(如果是非单例或延迟加载的Bean)
UserService userService = context.getBean("userService", UserService.class);
// 调用业务方法验证Bean功能正常
userService.queryUser(); // queryUser()是UserService中定义的业务方法
// 3. 关闭Spring容器(触发Bean销毁)
System.out.println("---------------------Spring容器开始关闭---------------------");
context.close(); // 调用close()方法会触发所有单例Bean的销毁回调
System.out.println("---------------------Spring容器关闭完成---------------------");
}
}
2. 测试类关键说明
-
容器选择:
- 使用
ClassPathXmlApplicationContext而非ApplicationContext接口,因为需要调用close()方法 - 也可以使用
AnnotationConfigApplicationContext进行基于注解的配置测试
- 使用
-
配置方式:
- XML配置示例(
applicationContext.xml):<bean id="userService" class="com.example.UserServiceImpl" init-method="customInit" destroy-method="customDestroy"> <property name="userDao" ref="userDao"/> </bean> <bean id="myBeanPostProcessor" class="com.example.MyBeanPostProcessor"/>
- XML配置示例(
-
Bean实现要求:
UserService需要实现InitializingBean,DisposableBean接口- 包含
@PostConstruct和@PreDestroy注解方法 - 定义XML配置中指定的
init-method和destroy-method
3. 预期控制台输出结果(完整顺序)
---------------------开始启动Spring容器---------------------
UserService:实例化阶段(调用无参构造函数)
UserService:属性注入阶段(设置userDao)
UserService:BeanNameAware回调(Bean名称为:userService)
UserService:ApplicationContextAware回调(获取ApplicationContext实例)
MyBeanPostProcessor:postProcessBeforeInitialization(userService)——初始化前处理
UserService:@PostConstruct注解初始化方法
UserService:InitializingBean回调(afterPropertiesSet())——属性设置完成后执行
UserService:自定义初始化方法(init-method)
MyBeanPostProcessor:postProcessAfterInitialization(userService)——初始化后处理
---------------------Spring容器启动完成,Bean就绪---------------------
UserService:执行业务方法(queryUser())
---------------------Spring容器开始关闭---------------------
UserService:@PreDestroy注解销毁方法
UserService:DisposableBean回调(destroy())
UserService:自定义销毁方法(destroy-method)
---------------------Spring容器关闭完成---------------------
4. 测试结果分析
-
初始化阶段验证:
- 实例化 → 属性注入 → Aware接口回调 → BeanPostProcessor前置处理 → 初始化方法 → BeanPostProcessor后置处理
- 初始化方法执行顺序:
@PostConstruct→afterPropertiesSet()→init-method
-
销毁阶段验证:
- 销毁方法执行顺序:
@PreDestroy→destroy()→destroy-method
- 销毁方法执行顺序:
-
关键观察点:
- BeanPostProcessor的两次调用分别在初始化前后
- 三种初始化方法的执行顺序
- 三种销毁方法的执行顺序
四、关键注意事项与常见问题
4.1 单例 Bean 与原型 Bean 的生命周期差异
4.1.1 单例 Bean(scope="singleton")
- 创建时机:容器启动时立即创建(默认情况下懒加载关闭),可通过配置
lazy-init="true"改为延迟初始化 - 销毁时机:容器关闭时自动销毁
- 生命周期管理:整个生命周期由 Spring 容器全程管理
- 实例数量:在整个容器生命周期内仅存在一个共享实例
- 典型应用场景:无状态的工具类、配置类、服务类等
4.2.2 原型 Bean(scope="prototype")
- 创建时机:容器不会在启动时自动创建实例,而是每次调用
getBean()时都会创建一个全新的独立实例 - 销毁时机:容器不会管理原型 Bean 的销毁,需要开发者自行处理资源释放
- 生命周期特点:
- 属性注入和初始化阶段(
@PostConstruct、InitializingBean、init-method)会正常执行 - 销毁阶段(
DisposableBean的destroy()方法、@PreDestroy注解方法、destroy-method配置)均不会被容器调用 - 长时间运行的原型 Bean 可能导致内存泄漏,需特别注意资源清理
- 属性注入和初始化阶段(
- 典型应用场景:需要保持独立状态的业务对象,如购物车、用户会话等
4.3.3 示例:配置原型 Bean
<!-- 原型Bean配置 -->
<bean id="prototypeBean"
class="com.example.bean.PrototypeBean"
scope="prototype"
init-method="init"
destroy-method="destroy"/>
测试代码:
// 每次调用都会创建新实例
PrototypeBean bean1 = context.getBean("prototypeBean");
PrototypeBean bean2 = context.getBean("prototypeBean");
System.out.println(bean1 == bean2); // 输出 false
// 容器关闭时不会调用destroy方法
context.close();
4.2 懒加载对 Bean 生命周期的影响
4.2.1 重要说明
- 原型 Bean 的特殊性:原型 Bean 始终处于"懒加载"状态,
lazy-init配置对其无效,因为原型 Bean 本身就是按需创建 - 单例 Bean 的懒加载控制:
- 默认情况(
lazy-init="false"):容器启动时完成实例化、属性注入和初始化("预初始化") - 设置
lazy-init="true":延迟到第一次getBean()调用时才执行完整初始化流程
- 默认情况(
4.2.2 示例:配置懒加载单例 Bean
<bean id="lazyBean"
class="com.example.bean.LazyBean"
lazy-init="true"/>
4.2.3 懒加载的优缺点分析
优点:
- 减少应用启动时的初始化时间
- 节省内存资源(延迟加载不立即使用的Bean)
缺点:
- 首次请求时可能产生延迟
- 可能掩盖某些初始化阶段的错误
4.3 BeanPostProcessor 的执行范围
4.3.1 执行特点
- 全局性:对所有 Spring 容器管理的 Bean 实例都生效(包括 Spring 内置 Bean)
- 执行阶段:
postProcessBeforeInitialization():在 Bean 初始化方法(@PostConstruct、InitializingBean等)之前执行postProcessAfterInitialization():在 Bean 初始化方法之后执行
4.3.2 过滤处理建议
当需要对特定 Bean 进行处理时,应在方法内部通过以下方式进行判断:
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 按类型过滤
if (bean instanceof UserService) {
// 特定处理逻辑
}
// 按名称过滤
if ("userService".equals(beanName)) {
// 特定处理逻辑
}
return bean;
}
4.4 构造函数注入与属性注入的生命周期顺序差异
4.4.1 构造函数注入特点
- 执行时机:在 Bean 实例化阶段(调用构造函数时)完成依赖注入
- 优点:
- 依赖关系明确,强制要求依赖项可用
- 便于实现不可变对象
- 缺点:
- 当依赖项较多时,构造函数会变得臃肿
- 循环依赖问题更难解决
4.4.2 属性注入特点
- 执行时机:在 Bean 实例化之后、Aware 接口回调之前完成
- 优点:
- 代码更简洁
- 更灵活地处理可选依赖
- 缺点:
- 依赖关系不如构造函数注入明确
- 可能导致部分空指针问题
4.4.3 示例对比
构造函数注入方式:
public class UserService {
private final UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
System.out.println("UserService:构造函数注入(实例化阶段)");
}
}
属性注入方式:
public class UserService {
@Autowired
private UserDao userDao;
public UserService() {
System.out.println("UserService:实例化(此时userDao尚未注入)");
}
@PostConstruct
public void init() {
System.out.println("UserService:初始化(此时userDao已注入)");
}
}
五、Spring Boot 中 Bean 生命周期的变化
1. 简化配置,减少 XML 依赖
Spring Boot 通过注解驱动开发极大简化了 Bean 的配置方式:
- 使用
@Component、@Service、@Repository、@Controller等注解替代传统 XML 中的<bean>定义 - 自定义初始化和销毁方法时,推荐使用 JSR-250 标准注解:
@PostConstruct:替代 XML 中的init-method属性@PreDestroy:替代 XML 中的destroy-method属性
- 示例代码:
@Service public class UserService { @PostConstruct public void init() { // 初始化逻辑 } @PreDestroy public void cleanup() { // 资源释放逻辑 } }
2. 自动配置类中的 Bean 生命周期
Spring Boot 自动配置机制对 Bean 生命周期的处理:
-
自动配置原理:
- 通过
spring-boot-autoconfigure模块中的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件加载配置类 - 例如
DataSourceAutoConfiguration会自动配置数据源
- 通过
-
自定义自动配置 Bean:
- 可通过
@Bean注解的initMethod和destroyMethod属性指定方法 - 或实现
InitializingBean和DisposableBean接口 - 完整配置示例:
@Configuration public class DataSourceConfig { @Bean(initMethod = "init", destroyMethod = "close") public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("password"); dataSource.setMaximumPoolSize(20); return dataSource; } }
- 可通过
-
生命周期阶段:
实例化 → 属性赋值 → 初始化前 (@PostConstruct) → 初始化 (InitializingBean) → 初始化后 (AOP 代理等) → 使用 → 销毁前 (@PreDestroy) → 销毁 (DisposableBean)
3. Spring Boot 2.x 中的注解兼容性
Java 9+ 的模块化影响
- 问题背景:
- JSR-250 (
javax.annotation包) 在 Java 9 被标记为可选模块 - 可能导致
@PostConstruct和@PreDestroy无法正常使用
- JSR-250 (
解决方案
-
Spring Boot 默认支持:
- 2.x 版本通过
spring-context模块内置了这些注解的实现 - 一般情况无需额外配置
- 2.x 版本通过
-
特殊情况处理:
- 若出现注解失效,检查以下可能:
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency> - 或检查是否误排除了相关依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <!-- 确保没有排除 spring-context --> </exclusions> </dependency>
- 若出现注解失效,检查以下可能:
-
验证方式:
@SpringBootTest public class LifecycleTest { @Autowired private UserService userService; @Test public void testAnnotations() { // 测试会自动触发 @PostConstruct 方法 assertNotNull(userService); } }
六、Bean 生命周期的实际应用场景
6.1资源初始化与释放
示例:初始化 Redis 连接
@Service
public class RedisService implements InitializingBean, DisposableBean {
private Jedis jedis;
private JedisPool jedisPool;
@Value("${redis.host}")
private String redisHost;
@Value("${redis.port}")
private int redisPort;
@Override
public void afterPropertiesSet() throws Exception {
// 初始化阶段:创建Redis连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(50);
poolConfig.setMaxIdle(20);
jedisPool = new JedisPool(poolConfig, redisHost, redisPort);
System.out.println("Redis连接池初始化完成,当前连接数:" + poolConfig.getMaxTotal());
// 测试连接
try (Jedis testConnection = jedisPool.getResource()) {
System.out.println("Redis连接测试成功,PING响应:" + testConnection.ping());
}
}
@Override
public void destroy() throws Exception {
// 销毁阶段:关闭连接池
if (jedisPool != null && !jedisPool.isClosed()) {
jedisPool.close();
System.out.println("Redis连接池已关闭,释放所有连接");
}
}
public Jedis getResource() {
return jedisPool.getResource();
}
}
典型应用场景
-
数据库连接管理:
- 在初始化阶段创建DataSource和连接池
- 在销毁阶段关闭连接池,释放所有连接
- 示例:HikariCP、DBCP等连接池的初始化
-
文件资源处理:
@Component public class FileProcessor { private BufferedWriter writer; @PostConstruct public void init() throws IOException { writer = new BufferedWriter(new FileWriter("app.log", true)); writer.write("\n=== 应用启动于 " + new Date() + " ===\n"); writer.flush(); } @PreDestroy public void cleanup() throws IOException { writer.write("=== 应用关闭于 " + new Date() + " ===\n"); writer.close(); } } -
线程池管理:
- 初始化时创建固定大小的线程池
- 销毁时执行shutdownNow()并等待任务完成
6.2Bean 属性校验
示例:属性依赖校验
@Component
public class DependencyCheckBeanPostProcessor implements BeanPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(DependencyCheckBeanPostProcessor.class);
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 检查所有标有@Required注解的字段
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Required.class)) {
field.setAccessible(true);
try {
Object value = field.get(bean);
if (value == null) {
String errorMsg = String.format("Bean '%s'的必需字段'%s'未注入!",
beanName, field.getName());
logger.error(errorMsg);
throw new IllegalStateException(errorMsg);
}
} catch (IllegalAccessException e) {
logger.error("检查字段访问失败", e);
}
}
}
// 检查方法级别的@Required注解
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Required.class) &&
method.getParameterCount() == 1) {
method.setAccessible(true);
try {
Object value = method.invoke(bean);
if (value == null) {
String errorMsg = String.format("Bean '%s'的必需方法'%s'返回null!",
beanName, method.getName());
logger.error(errorMsg);
throw new IllegalStateException(errorMsg);
}
} catch (Exception e) {
logger.error("检查方法调用失败", e);
}
}
}
return bean;
}
}
校验类型扩展
-
基本类型校验:
if (field.getType() == int.class && (int)field.get(bean) == 0) { throw new IllegalStateException("整型字段不能为0"); } -
集合校验:
if (field.getType().isAssignableFrom(Collection.class)) { Collection<?> collection = (Collection<?>) field.get(bean); if (collection == null || collection.isEmpty()) { throw new IllegalStateException("集合字段不能为空"); } } -
自定义注解校验:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ValidEmail { String message() default "无效的邮箱格式"; }
6.3AOP 代理增强
代理创建过程详解
Spring通过AbstractAutoProxyCreator实现代理创建:
-
初始化阶段:
public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { // 包装目标对象 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } -
代理选择策略:
- 如果目标类实现了接口,默认使用JDK动态代理
- 如果目标类没有实现接口,使用CGLIB代理
- 可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用CGLIB
典型AOP应用场景
-
事务管理:
@Service public class OrderService { @Transactional public void createOrder(Order order) { // 业务逻辑 } } -
性能监控:
@Aspect @Component public class PerformanceMonitorAspect { @Around("execution(* com.example.service.*.*(..))") public Object monitor(ProceedingJoinPoint pjp) throws Throwable { long start = System.currentTimeMillis(); Object result = pjp.proceed(); long duration = System.currentTimeMillis() - start; System.out.println(pjp.getSignature() + " executed in " + duration + "ms"); return result; } } -
安全控制:
@Aspect @Component public class SecurityAspect { @Before("@annotation(requiresPermission)") public void checkPermission(RequiresPermission requiresPermission) { if (!SecurityContext.hasPermission(requiresPermission.value())) { throw new AccessDeniedException("权限不足"); } } } -
缓存处理:
@Aspect @Component public class CacheAspect { @Around("@annotation(cacheable)") public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable { String key = generateKey(pjp, cacheable); Object value = cache.get(key); if (value == null) { value = pjp.proceed(); cache.put(key, value, cacheable.ttl()); } return value; } }
生命周期注意事项
-
代理对象与原对象:
- 容器中保存的是代理对象
- 原对象被代理对象持有为目标源(target source)
- 通过
AopContext.currentProxy()可获取当前代理
-
初始化顺序:
@Service public class OrderService { @Autowired private PaymentService paymentService; // 注入的是代理对象 @PostConstruct public void init() { // 此时所有依赖都是代理对象 } } -
自调用问题:
public void methodA() { methodB(); // 直接调用会绕过代理 ((OrderService)AopContext.currentProxy()).methodB(); // 正确方式 } @Transactional public void methodB() { // 事务逻辑 }
1265

被折叠的 条评论
为什么被折叠?



