【Day40】Spring Bean:生命周期、作用域与装配方式
本文收录于「Java 学习日记」专栏,聚焦 Spring 框架的核心载体 ——Bean,从生命周期全流程、作用域控制到多种装配方式,帮你彻底搞懂 “Spring 如何管理对象”,夯实框架底层认知~
一、为什么要深入学习 Spring Bean?
在上一篇注解学习中,我们知道@Service/@Component能让 Spring 管理对象,但背后有三个核心问题需要搞清楚:
- 生命周期:Spring 创建 Bean 时,从 “出生” 到 “销毁” 经历了哪些阶段?如何在关键节点自定义逻辑?
- 作用域:为什么默认 Bean 是单例?原型 Bean 和单例 Bean 的使用场景有何不同?
- 装配方式:除了
@Autowired,还有哪些方式能把 Bean 组装到一起?不同方式的优缺点是什么?
理解这些问题,能帮你:
- 解决 Bean 注入失败、生命周期方法不执行等常见问题;
- 根据业务场景选择合适的 Bean 作用域,避免线程安全问题;
- 灵活选择 Bean 装配方式,适配不同的开发场景(如第三方类集成)。
今天这篇日记,我们从 “生命周期→作用域→装配方式” 逐步拆解,结合实战案例让你对 Spring Bean 建立完整认知。
二、Spring Bean 的完整生命周期(核心)
Spring Bean 的生命周期是指从容器启动创建 Bean到容器关闭销毁 Bean的全流程,核心分为 8 个阶段(单例 Bean),用流程图直观展示:
预览
graph TD
A[1. 实例化Bean] --> B[2. 设置Bean属性(依赖注入)]
B --> C[3. 调用BeanNameAware的setBeanName()]
C --> D[4. 调用BeanFactoryAware的setBeanFactory()]
D --> E[5. 调用ApplicationContextAware的setApplicationContext()]
E --> F[6. 调用BeanPostProcessor的postProcessBeforeInitialization()]
F --> G[7. 调用InitializingBean的afterPropertiesSet() / @PostConstruct]
G --> H[8. 调用BeanPostProcessor的postProcessAfterInitialization()]
H --> I[9. Bean就绪,对外提供使用]
I --> J[10. 容器关闭:调用DisposableBean的destroy() / @PreDestroy]
1. 生命周期核心阶段详解
| 阶段 | 核心操作 | 自定义方式 |
|---|---|---|
| 实例化 Bean | Spring 通过反射创建 Bean 对象(调用无参构造) | 无(由 Spring 自动完成) |
| 设置 Bean 属性 | 注入依赖的 Bean(如@Autowired注入) |
无(依赖注入逻辑) |
| 感知容器(Aware 接口) | Bean 感知自身在容器中的信息(如 Bean 名称、容器对象) | 实现BeanNameAware/BeanFactoryAware等接口 |
| 初始化前处理 | 全局 Bean 后置处理器,对所有 Bean 生效 | 实现BeanPostProcessor接口,重写postProcessBeforeInitialization |
| 初始化 | Bean 初始化核心阶段,执行自定义初始化逻辑 | 1. 实现InitializingBean接口;2. 标注@PostConstruct;3. XML 配置init-method |
| 初始化后处理 | 全局 Bean 后置处理器,初始化后增强 Bean | 实现BeanPostProcessor接口,重写postProcessAfterInitialization |
| 就绪使用 | Bean 对外提供服务(如被注入到 Controller/Service) | 业务代码调用 Bean 方法 |
| 销毁 | 容器关闭时执行清理逻辑 | 1. 实现DisposableBean接口;2. 标注@PreDestroy;3. XML 配置destroy-method |
2. 生命周期实战案例
步骤 1:编写自定义 Bean,实现生命周期接口 + 注解
java
运行
package com.example.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component("userBean")
public class UserBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
private String username;
// 1. 无参构造(实例化阶段调用)
public UserBean() {
System.out.println("【1. 实例化】UserBean无参构造被调用");
}
// 2. Setter方法(属性注入阶段调用)
public void setUsername(String username) {
this.username = username;
System.out.println("【2. 设置属性】注入username:" + username);
}
// 3. BeanNameAware:获取Bean名称
@Override
public void setBeanName(String name) {
System.out.println("【3. 感知BeanName】Bean名称:" + name);
}
// 4. BeanFactoryAware:获取BeanFactory
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【4. 感知BeanFactory】BeanFactory:" + beanFactory.getClass().getSimpleName());
}
// 5. ApplicationContextAware:获取ApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("【5. 感知ApplicationContext】容器:" + applicationContext.getClass().getSimpleName());
}
// 6. 初始化前(@PostConstruct之前)
@PostConstruct
public void postConstruct() {
System.out.println("【7. 初始化-@PostConstruct】执行初始化逻辑");
}
// 7. InitializingBean:属性设置完成后初始化
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【7. 初始化-afterPropertiesSet】执行初始化逻辑");
}
// 自定义初始化方法(XML配置init-method时用)
public void initMethod() {
System.out.println("【7. 初始化-initMethod】执行自定义初始化");
}
// 业务方法
public void sayHello() {
System.out.println("【9. 就绪使用】Hello " + username);
}
// 8. DisposableBean:销毁Bean
@Override
public void destroy() throws Exception {
System.out.println("【10. 销毁-destroy】执行销毁逻辑");
}
// 自定义销毁方法(XML配置destroy-method时用)
@PreDestroy
public void preDestroy() {
System.out.println("【10. 销毁-@PreDestroy】执行销毁逻辑");
}
}
步骤 2:编写 Bean 后置处理器(全局生效)
java
运行
package com.example.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
// 初始化前处理
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("userBean".equals(beanName)) {
System.out.println("【6. 初始化前处理】BeanPostProcessor-before");
}
return bean;
}
// 初始化后处理
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("userBean".equals(beanName)) {
System.out.println("【8. 初始化后处理】BeanPostProcessor-after");
}
return bean;
}
}
步骤 3:配置类 + 测试类
java
运行
// 配置类
package com.example.config;
import com.example.bean.UserBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.example") // 扫描注解
public class SpringConfig {
// 注册Bean并设置属性
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public UserBean userBean() {
UserBean userBean = new UserBean();
userBean.setUsername("Java日记");
return userBean;
}
}
// 测试类
package com.example.test;
import com.example.bean.UserBean;
import com.example.config.SpringConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class BeanLifeCycleTest {
public static void main(String[] args) {
// 1. 创建容器(触发Bean实例化、初始化)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 2. 获取Bean并使用
UserBean userBean = context.getBean(UserBean.class);
userBean.sayHello();
// 3. 关闭容器(触发Bean销毁)
context.close();
}
}
步骤 4:运行结果(核心流程验证)
plaintext
【1. 实例化】UserBean无参构造被调用
【2. 设置属性】注入username:Java日记
【3. 感知BeanName】Bean名称:userBean
【4. 感知BeanFactory】BeanFactory:DefaultListableBeanFactory
【5. 感知ApplicationContext】容器:AnnotationConfigApplicationContext
【6. 初始化前处理】BeanPostProcessor-before
【7. 初始化-@PostConstruct】执行初始化逻辑
【7. 初始化-afterPropertiesSet】执行初始化逻辑
【7. 初始化-initMethod】执行自定义初始化
【8. 初始化后处理】BeanPostProcessor-after
【9. 就绪使用】Hello Java日记
【10. 销毁-@PreDestroy】执行销毁逻辑
【10. 销毁-destroy】执行销毁逻辑
3. 生命周期关键注意点
- 初始化方法优先级:
@PostConstruct>InitializingBean.afterPropertiesSet()> XML/@Bean指定的init-method; - 销毁方法优先级:
@PreDestroy>DisposableBean.destroy()> XML/@Bean指定的destroy-method; - 原型 Bean 生命周期:原型 Bean 仅执行 “实例化→属性设置→初始化”,容器不管理销毁(需手动调用);
- BeanPostProcessor:对容器中所有 Bean 生效,是 Spring AOP、注解解析的核心底层机制。
三、Spring Bean 的作用域(Scope)
Bean 的作用域决定了Bean 实例的创建时机和存活范围,Spring 提供 5 种核心作用域(常用 2 种):
| 作用域 | 中文名称 | 核心特点 | 适用场景 |
|---|---|---|---|
singleton |
单例(默认) | 容器启动时创建,全局唯一,容器关闭时销毁 | 无状态 Bean(如 Service、DAO、工具类) |
prototype |
原型 | 每次getBean()/ 注入时创建新实例,容器不管理销毁 |
有状态 Bean(如含成员变量的对象、请求级数据载体) |
request |
请求级 | Web 环境下,每个 HTTP 请求创建一个实例 | Web 请求级数据(如请求参数封装) |
session |
会话级 | Web 环境下,每个 HTTP 会话创建一个实例 | 会话级数据(如用户登录信息) |
application |
应用级 | Web 环境下,整个 Web 应用共享一个实例(等价于 singleton) | 全局应用配置 |
1. 作用域实战(单例 vs 原型)
步骤 1:定义不同作用域的 Bean
java
运行
// 单例Bean(默认)
@Component
@Scope("singleton")
public class SingletonBean {
private int count = 0;
public void add() {
count++;
System.out.println("单例Bean count:" + count);
}
}
// 原型Bean
@Component
@Scope("prototype")
public class PrototypeBean {
private int count = 0;
public void add() {
count++;
System.out.println("原型Bean count:" + count);
}
}
步骤 2:测试作用域差异
java
运行
public class BeanScopeTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 单例Bean:多次获取是同一个对象
SingletonBean s1 = context.getBean(SingletonBean.class);
SingletonBean s2 = context.getBean(SingletonBean.class);
s1.add(); // 输出:单例Bean count:1
s2.add(); // 输出:单例Bean count:2
System.out.println("单例Bean是否同一对象:" + (s1 == s2)); // true
// 原型Bean:多次获取是不同对象
PrototypeBean p1 = context.getBean(PrototypeBean.class);
PrototypeBean p2 = context.getBean(PrototypeBean.class);
p1.add(); // 输出:原型Bean count:1
p2.add(); // 输出:原型Bean count:1
System.out.println("原型Bean是否同一对象:" + (p1 == p2)); // false
context.close();
}
}
2. 作用域避坑指南
- 单例 Bean 线程安全:单例 Bean 是全局共享的,若包含可变成员变量(如用户信息),会导致线程安全问题,解决方案:
- 避免在单例 Bean 中定义可变成员变量;
- 改用原型 Bean;
- 使用 ThreadLocal 存储线程私有数据。
- 原型 Bean 注入陷阱:单例 Bean 注入原型 Bean 时,原型 Bean 只会被注入一次(单例 Bean 初始化时),后续调用仍是同一个原型 Bean,解决方案:
java
运行
// 单例Bean中获取原型Bean(每次获取新实例) @Component public class SingletonBean { @Autowired private ApplicationContext context; public PrototypeBean getPrototypeBean() { return context.getBean(PrototypeBean.class); } } - Web 作用域使用条件:
request/session作用域仅在 Web 环境下生效,需引入 Spring Web 依赖,否则报错。
四、Spring Bean 的装配方式(依赖注入)
Bean 装配是指 “将依赖的 Bean 注入到目标 Bean 中”,Spring 提供 4 种核心装配方式,适配不同场景:
1. 手动装配(XML 配置)
适用场景:老项目、需要精确控制 Bean 创建逻辑的场景。
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. 构造器装配 -->
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserServiceImpl">
<constructor-arg ref="userDao"/> <!-- 构造器注入 -->
</bean>
<!-- 2. Setter装配 -->
<bean id="userController" class="com.example.controller.UserController">
<property name="userService" ref="userService"/> <!-- Setter注入 -->
</bean>
</beans>
2. 注解装配(主流)
适用场景:新项目、快速开发,核心注解:@Autowired/@Resource/@Qualifier。
java
运行
@Service
public class UserServiceImpl implements UserService {
// 方式1:字段注入(最简洁,推荐)
@Autowired
private UserDao userDao;
// 方式2:Setter注入(兼容老版本)
// @Resource(name = "userDao")
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
// 方式3:构造器注入(推荐用于必须依赖)
// public UserServiceImpl(@Qualifier("userDao") UserDao userDao) {
// this.userDao = userDao;
// }
}
3. Java 配置装配(@Bean)
适用场景:第三方类集成(如数据源、RedisTemplate)、需要手动创建 Bean 的场景。
java
运行
@Configuration
public class BeanConfig {
// 装配第三方类(如Druid数据源)
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
// 依赖注入:方法参数自动装配
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
4. 自动装配(autowire)
适用场景:简化 XML 配置,Spring 自动匹配依赖(不推荐,可读性差)。
xml
<!-- autowire="byType":按类型自动装配 -->
<bean id="userService" class="com.example.service.UserServiceImpl" autowire="byType"/>
<!-- autowire="byName":按名称自动装配(匹配setter方法名) -->
<bean id="userController" class="com.example.controller.UserController" autowire="byName"/>
5. 装配方式对比与最佳实践
| 装配方式 | 优点 | 缺点 | 最佳实践 |
|---|---|---|---|
| 字段注入(@Autowired) | 代码简洁,开发效率高 | 无法注入 final 字段,单元测试需 Mock | 日常业务开发(Service/Controller) |
| 构造器注入 | 支持 final 字段,依赖明确,线程安全 | 依赖多时代码冗长 | 核心组件(如配置类、第三方集成) |
| Setter 注入 | 可选依赖,灵活配置 | 依赖可被多次修改,易出错 | 非必须依赖的场景 |
| Java 配置(@Bean) | 控制 Bean 创建全流程,适配第三方类 | 代码量略多 | 第三方组件集成(数据源、连接池) |
五、今日实战小任务
- 基于生命周期案例,新增一个
BeanPostProcessor,实现 “给所有 Bean 的 username 属性添加前缀【Spring】”; - 编写一个单例 Bean,内部包含可变成员变量,模拟多线程访问,验证线程安全问题,并通过 ThreadLocal 解决;
- 用 4 种装配方式(XML、字段注入、构造器注入、@Bean)分别实现 “UserController → UserService → UserDao” 的依赖注入。
总结
- Spring Bean 生命周期核心流程:实例化→属性注入→感知容器→初始化前处理→初始化→初始化后处理→就绪使用→销毁,可通过
@PostConstruct/InitializingBean等自定义初始化逻辑; - Bean 作用域中,
singleton(单例)是默认值,适用于无状态 Bean,prototype(原型)适用于有状态 Bean,需注意原型 Bean 注入单例 Bean 的陷阱; - Bean 装配方式有 4 种:XML 手动装配、注解装配(主流)、Java 配置装配、自动装配,字段注入适合日常开发,构造器注入适合核心组件,@Bean 适合第三方类集成;
- 核心避坑点:单例 Bean 的线程安全问题、原型 Bean 的注入陷阱、生命周期方法的执行顺序。
下一篇【Day41】预告:SpringMVC 核心流程:DispatcherServlet 请求处理全解析,关注专栏从 Spring 核心过渡到 Web 框架实战~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!
更多推荐



所有评论(0)