适合人群 :Java初学者、Spring框架学习者、面试准备者
关键词:Spring Bean、生命周期、作用域、依赖注入、回调方法
📚 目录
- 什么是Bean?
- Bean是否单例?
- Bean的生命周期详解
- 单例和非单例Bean的生命周期对比
- [Spring Bean的作用域](#Spring Bean的作用域)
- Bean加载/销毁前后的回调方法
- 完整代码示例
- 常见面试问题
- 总结
1. 什么是Bean?
1.1 Bean的基本概念
在Spring框架中,Bean是一个由Spring IoC容器管理的对象。
🔍 通俗理解 :
想象你开了一家咖啡店(Spring容器),店里的各种设备(咖啡机、磨豆机、收银机等)就是Bean。你不需要自己去制造这些设备,而是由供应商(Spring)统一提供和管理。
关键概念解释:
- IoC(Inversion of Control):控制反转,即对象的创建和管理权从程序员转移到Spring容器
- Spring容器:负责创建、配置和管理Bean的容器,主要有BeanFactory和ApplicationContext两种
java
// 传统方式:程序员自己创建对象
public class TraditionalWay {
public static void main(String[] args) {
// 我们自己new对象,控制权在程序员手中
UserService userService = new UserService();
userService.doSomething();
}
}
// Spring方式:由容器创建和管理对象
@Component // 告诉Spring:这是一个Bean,请帮我管理
public class UserService {
public void doSomething() {
System.out.println("执行业务逻辑");
}
}
1.2 为什么需要Bean?
使用Spring Bean的优势:
- ✅ 解耦:降低组件之间的依赖关系
- ✅ 易于测试:可以方便地进行单元测试和Mock
- ✅ 统一管理:对象的创建、配置、销毁都由容器统一处理
- ✅ AOP支持:便于实现面向切面编程
2. Bean是否单例?
2.1 默认情况
答案:是的,Spring中的Bean默认是单例(Singleton)的。
🔍 通俗理解 :
单例就像你家里的电视机,全家人共用一台,而不是每个人都有一台自己的电视。
java
@Component
public class SingletonBean {
// 默认情况下,Spring容器中只会创建这个类的一个实例
// 所有需要使用这个Bean的地方,拿到的都是同一个对象
}
2.2 验证Bean是否单例
java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
@Component
class MyBean {
public MyBean() {
System.out.println("MyBean构造方法被调用,对象创建时间:" + System.currentTimeMillis());
}
}
public class SingletonTest {
public static void main(String[] args) {
// 创建Spring容器
ApplicationContext context = new AnnotationConfigApplicationContext(MyBean.class);
// 从容器中获取Bean,第一次
MyBean bean1 = context.getBean(MyBean.class);
System.out.println("bean1的内存地址:" + bean1);
// 从容器中获取Bean,第二次
MyBean bean2 = context.getBean(MyBean.class);
System.out.println("bean2的内存地址:" + bean2);
// 判断是否是同一个对象
System.out.println("bean1和bean2是同一个对象吗?" + (bean1 == bean2));
}
}
/* 运行结果:
* MyBean构造方法被调用,对象创建时间:1673856123456
* bean1的内存地址:com.example.MyBean@15db9742
* bean2的内存地址:com.example.MyBean@15db9742
* bean1和bean2是同一个对象吗?true
*
* 结论:构造方法只被调用了一次,两次获取的Bean内存地址相同,证明是单例
*/
2.3 单例的优缺点
优点:
- 💡 节省内存资源(只创建一个对象)
- 💡 提高性能(避免重复创建)
- 💡 便于共享数据
缺点:
- ⚠️ 线程安全问题(多线程访问同一对象需要注意)
- ⚠️ 不适合有状态的Bean(即成员变量会变化的Bean)
java
// ❌ 错误示例:单例Bean中使用成员变量会有线程安全问题
@Component
public class UnsafeSingletonBean {
// 这是成员变量,在单例模式下,多线程会共享这个变量,可能导致数据混乱
private int count = 0;
public void increment() {
count++; // 多线程访问会有并发问题
}
}
// ✅ 正确示例1:无状态的单例Bean
@Component
public class SafeSingletonBean {
// 没有成员变量,只有方法,是线程安全的
public int add(int a, int b) {
return a + b;
}
}
// ✅ 正确示例2:使用ThreadLocal保证线程安全
@Component
public class ThreadLocalBean {
// ThreadLocal为每个线程提供独立的变量副本
private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);
public void increment() {
count.set(count.get() + 1); // 每个线程有自己的count
}
}
3. Bean的生命周期详解
Bean的生命周期是指Bean从创建到销毁的整个过程,这个过程由Spring容器管理。
3.1 Bean生命周期流程图
┌─────────────────────────────────────────────────────────────┐
│ Spring容器启动 │
└───────────────────────┬─────────────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ 1. 实例化Bean(调用构造方法) │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 2. 设置Bean的属性(依赖注入) │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 3. 调用BeanNameAware接口 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 4. 调用BeanFactoryAware接口 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 5. 调用ApplicationContextAware│
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 6. BeanPostProcessor前置处理 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 7. 调用@PostConstruct方法 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 8. 调用InitializingBean接口 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 9. 调用自定义init-method │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 10. BeanPostProcessor后置处理 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ Bean可以使用了! │
│ (正常业务逻辑) │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 容器关闭,开始销毁Bean │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 11. 调用@PreDestroy方法 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 12. 调用DisposableBean接口 │
└───────────┬───────────────────┘
│
▼
┌───────────────────────────────┐
│ 13. 调用自定义destroy-method │
└───────────┬───────────────────┘
│
▼
┌─────────┐
│ Bean销毁 │
└─────────┘
3.2 生命周期各阶段详解
阶段1:实例化(Instantiation)
Spring容器通过反射机制调用Bean的构造方法创建对象。
java
@Component
public class LifeCycleBean {
// 1. 实例化阶段:构造方法被调用
public LifeCycleBean() {
System.out.println("【1-构造方法】Bean对象被创建");
}
}
🔍 反射机制 :Java的一种特性,可以在运行时动态地创建对象、调用方法等。
就像照镜子一样,可以看到类的内部结构(方法、属性等)并进行操作。
阶段2:属性赋值(Populate Properties)
Spring容器将配置文件或注解中定义的属性值注入到Bean中。
java
@Component
public class LifeCycleBean {
// 2. 属性赋值阶段:依赖注入
@Value("${user.name:张三}") // 从配置文件读取user.name,默认值为"张三"
private String userName;
@Autowired // 自动注入其他Bean
private UserService userService;
public LifeCycleBean() {
System.out.println("【1-构造方法】此时userName = " + userName); // null
}
}
🔍 依赖注入(DI - Dependency Injection) :
不用自己new对象,而是让Spring自动把需要的对象"注入"进来。
就像点外卖,你不用自己做饭,外卖平台把饭送到你手上。
阶段3-5:Aware接口回调
如果Bean实现了某些Aware接口,Spring会调用相应的方法,让Bean感知到Spring容器的信息。
java
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class LifeCycleBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
// 3. BeanNameAware:让Bean知道自己在容器中的名字
@Override
public void setBeanName(String name) {
System.out.println("【3-BeanNameAware】Bean的名字是:" + name);
}
// 4. BeanFactoryAware:让Bean获取到BeanFactory(Bean工厂)
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【4-BeanFactoryAware】获取到BeanFactory:" + beanFactory);
}
// 5. ApplicationContextAware:让Bean获取到ApplicationContext(应用上下文)
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("【5-ApplicationContextAware】获取到ApplicationContext:" + applicationContext);
}
}
🔍 Aware接口的作用 :
让Bean可以感知到Spring容器的存在,获取容器相关的信息。
就像给Bean装上了"感应器",能感知周围的环境。
阶段6、10:BeanPostProcessor(Bean后置处理器)
这是Spring提供的一个扩展点,可以在Bean初始化前后进行自定义处理。
java
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* Bean后置处理器:会对容器中所有的Bean生效
* 可以在Bean初始化前后进行自定义操作
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
// 6. 初始化之前调用
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("【6-BeanPostProcessor前置】处理Bean:" + beanName);
// 可以对Bean进行修改或包装,比如返回代理对象
return bean; // 返回原始Bean或修改后的Bean
}
// 10. 初始化之后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("【10-BeanPostProcessor后置】处理Bean:" + beanName);
// AOP就是在这个阶段实现的,返回代理对象
return bean;
}
}
🔍 BeanPostProcessor的应用场景:
- AOP代理对象的创建
- Bean属性的修改或验证
- 日志记录、性能监控等
阶段7:@PostConstruct
使用@PostConstruct注解的方法会在Bean属性赋值完成后、初始化方法之前执行。
java
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
@Component
public class LifeCycleBean {
@Value("${user.name:张三}")
private String userName;
// 7. @PostConstruct:初始化方法(JSR-250规范)
@PostConstruct
public void postConstruct() {
System.out.println("【7-@PostConstruct】Bean初始化,userName = " + userName);
// 这里可以做一些初始化工作,比如资源加载、数据预处理等
}
}
阶段8:InitializingBean接口
实现InitializingBean接口的Bean,会在属性设置完成后调用afterPropertiesSet()方法。
java
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class LifeCycleBean implements InitializingBean {
// 8. InitializingBean接口:Spring提供的初始化方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【8-InitializingBean】afterPropertiesSet方法被调用");
// 可以在这里进行一些初始化操作
}
}
阶段9:自定义init-method
可以通过配置或注解指定自定义的初始化方法。
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 方式1:使用@Bean注解的initMethod属性
@Configuration
public class BeanConfig {
@Bean(initMethod = "customInit")
public LifeCycleBean lifeCycleBean() {
return new LifeCycleBean();
}
}
public class LifeCycleBean {
// 9. 自定义初始化方法
public void customInit() {
System.out.println("【9-自定义init-method】customInit方法被调用");
}
}
阶段11:@PreDestroy
使用@PreDestroy注解的方法会在Bean销毁之前执行。
java
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class LifeCycleBean {
// 11. @PreDestroy:销毁前方法(JSR-250规范)
@PreDestroy
public void preDestroy() {
System.out.println("【11-@PreDestroy】Bean即将销毁");
// 这里可以做清理工作,比如关闭资源、释放连接等
}
}
阶段12:DisposableBean接口
实现DisposableBean接口的Bean,会在销毁时调用destroy()方法。
java
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class LifeCycleBean implements DisposableBean {
// 12. DisposableBean接口:Spring提供的销毁方法
@Override
public void destroy() throws Exception {
System.out.println("【12-DisposableBean】destroy方法被调用");
// 释放资源
}
}
阶段13:自定义destroy-method
可以通过配置或注解指定自定义的销毁方法。
java
@Configuration
public class BeanConfig {
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public LifeCycleBean lifeCycleBean() {
return new LifeCycleBean();
}
}
public class LifeCycleBean {
// 13. 自定义销毁方法
public void customDestroy() {
System.out.println("【13-自定义destroy-method】customDestroy方法被调用");
}
}
3.3 初始化和销毁方法的执行顺序
初始化方法执行顺序:
@PostConstructInitializingBean.afterPropertiesSet()- 自定义
init-method
销毁方法执行顺序:
@PreDestroyDisposableBean.destroy()- 自定义
destroy-method
💡 最佳实践:
- 优先使用
@PostConstruct和@PreDestroy,因为它们是JSR-250标准,不依赖Spring- 避免同时使用多种初始化/销毁方法,选择一种即可
4. 单例和非单例Bean的生命周期对比
4.1 生命周期差异
| 对比项 | 单例Bean(Singleton) | 原型Bean(Prototype) |
|---|---|---|
| 创建时机 | 容器启动时创建 | 每次getBean时创建 |
| 销毁时机 | 容器关闭时销毁 | 不由容器销毁 |
| 销毁方法 | 会调用@PreDestroy等 | 不会调用销毁方法 |
| 内存占用 | 只有一个实例 | 每次创建新实例 |
| 线程安全 | 需要注意线程安全 | 天然线程安全 |
4.2 重点:Prototype Bean不会被销毁
这是一个很重要的知识点!
java
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
@Component
@Scope("prototype") // 设置为原型作用域
public class PrototypeBean {
public PrototypeBean() {
System.out.println("PrototypeBean被创建");
}
@PreDestroy // ⚠️ 注意:这个方法不会被调用!
public void destroy() {
System.out.println("PrototypeBean被销毁");
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(PrototypeBean.class);
// 每次获取都会创建新对象
PrototypeBean bean1 = context.getBean(PrototypeBean.class); // 输出:PrototypeBean被创建
PrototypeBean bean2 = context.getBean(PrototypeBean.class); // 输出:PrototypeBean被创建
System.out.println("bean1 == bean2? " + (bean1 == bean2)); // false
// 关闭容器
((AnnotationConfigApplicationContext) context).close();
// ⚠️ 注意:不会输出"PrototypeBean被销毁"
}
}
🔍 为什么Prototype Bean不会被销毁?
因为Spring容器只负责创建Prototype Bean,创建完成后就不再管理它的生命周期了。
就像工厂生产产品,产品卖出去后,工厂就不管这个产品的后续了,由买家自己负责。
解决方案:如果需要手动管理Prototype Bean的销毁,可以这样做:
java
import org.springframework.beans.factory.DisposableBean;
@Component
@Scope("prototype")
public class PrototypeBean implements DisposableBean {
// 手动调用这个方法来销毁
@Override
public void destroy() throws Exception {
System.out.println("手动销毁PrototypeBean");
// 清理资源
}
}
// 使用时
PrototypeBean bean = context.getBean(PrototypeBean.class);
// ... 使用bean
bean.destroy(); // 手动调用销毁方法
5. Spring Bean的作用域
5.1 什么是作用域?
**作用域(Scope)**决定了Bean的创建策略和生命周期。
🔍 通俗理解 :
作用域就像是Bean的"使用范围"。
- 单例:全公司共用一台打印机
- 原型:每个人都有自己的笔记本电脑
5.2 Spring的六种作用域
| 作用域 | 说明 | 使用场景 |
|---|---|---|
| singleton | 单例(默认) | 无状态的Bean,如Service、DAO |
| prototype | 原型,每次创建新实例 | 有状态的Bean,如临时对象 |
| request | 每个HTTP请求一个实例 | Web应用,请求相关的数据 |
| session | 每个HTTP会话一个实例 | Web应用,用户会话数据 |
| application | 每个ServletContext一个实例 | Web应用,全局共享数据 |
| websocket | 每个WebSocket一个实例 | WebSocket应用 |
⚠️ 注意:后四种作用域只在Web应用中有效!
5.3 各作用域的代码示例
Singleton(单例)
java
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("singleton") // 或者不写,默认就是singleton
public class SingletonBean {
private int count = 0;
public void increment() {
count++;
System.out.println("当前count值:" + count);
}
}
// 测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SingletonBean.class);
SingletonBean bean1 = context.getBean(SingletonBean.class);
SingletonBean bean2 = context.getBean(SingletonBean.class);
bean1.increment(); // 输出:当前count值:1
bean2.increment(); // 输出:当前count值:2(因为是同一个对象)
System.out.println("bean1 == bean2? " + (bean1 == bean2)); // true
}
}
Prototype(原型)
java
@Component
@Scope("prototype") // 设置为原型作用域
public class PrototypeBean {
private int count = 0;
public void increment() {
count++;
System.out.println("当前count值:" + count);
}
}
// 测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(PrototypeBean.class);
PrototypeBean bean1 = context.getBean(PrototypeBean.class);
PrototypeBean bean2 = context.getBean(PrototypeBean.class);
bean1.increment(); // 输出:当前count值:1
bean2.increment(); // 输出:当前count值:1(因为是不同对象)
System.out.println("bean1 == bean2? " + (bean1 == bean2)); // false
}
}
Request(请求作用域)
java
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST) // 或者 @Scope("request")
public class RequestBean {
private String requestId;
public RequestBean() {
// 每个HTTP请求都会创建一个新的RequestBean实例
this.requestId = "Request-" + System.currentTimeMillis();
System.out.println("创建RequestBean:" + requestId);
}
public String getRequestId() {
return requestId;
}
}
// 在Controller中使用
@RestController
public class TestController {
@Autowired
private RequestBean requestBean; // 每个请求注入的都是新实例
@GetMapping("/test")
public String test() {
return "Request ID: " + requestBean.getRequestId();
}
}
📝 Request作用域的典型应用:
- 存储当前请求的上下文信息
- 记录请求的日志信息
- 临时存储请求参数
Session(会话作用域)
java
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION) // 或者 @Scope("session")
public class SessionBean {
private String sessionId;
private String username;
public SessionBean() {
// 每个用户会话创建一个SessionBean实例
this.sessionId = "Session-" + System.currentTimeMillis();
System.out.println("创建SessionBean:" + sessionId);
}
// getter和setter
}
// 在Controller中使用
@RestController
public class UserController {
@Autowired
private SessionBean sessionBean; // 同一个会话中获取的是同一个实例
@PostMapping("/login")
public String login(@RequestParam String username) {
sessionBean.setUsername(username);
return "登录成功,Session ID: " + sessionBean.getSessionId();
}
@GetMapping("/userInfo")
public String getUserInfo() {
return "用户名:" + sessionBean.getUsername() +
", Session ID: " + sessionBean.getSessionId();
}
}
📝 Session作用域的典型应用:
- 用户登录信息
- 购物车数据
- 用户偏好设置
5.4 如何选择合适的作用域?
选择作用域的决策树:
是否是Web应用?
├─ 否 → 是否需要共享实例?
│ ├─ 是 → singleton
│ └─ 否 → prototype
│
└─ 是 → 数据的生命周期是什么?
├─ 整个应用生命周期 → singleton 或 application
├─ 用户会话期间 → session
├─ 单个HTTP请求 → request
└─ 每次使用都要新建 → prototype
选择建议:
- ✅ Service层、DAO层:使用singleton(默认)
- ✅ Controller层:使用singleton(默认),Spring MVC会处理线程安全
- ✅ 有状态的临时对象:使用prototype
- ✅ 用户相关数据:使用session
- ✅ 请求相关数据:使用request
6. Bean加载/销毁前后的回调方法
在Spring中,有多种方式可以在Bean加载和销毁前后执行自定义逻辑。
6.1 回调方法汇总
| 方式 | 类型 | 优先级 | 是否依赖Spring |
|---|---|---|---|
| @PostConstruct | 初始化 | 1(最先执行) | 否(JSR-250标准) |
| InitializingBean | 初始化 | 2 | 是 |
| init-method | 初始化 | 3 | 是 |
| @PreDestroy | 销毁 | 1(最先执行) | 否(JSR-250标准) |
| DisposableBean | 销毁 | 2 | 是 |
| destroy-method | 销毁 | 3 | 是 |
6.2 方式1:使用@PostConstruct和@PreDestroy(推荐)
java
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
/**
* 使用JSR-250标准注解
* 优点:不依赖Spring,代码更通用
*/
@Component
public class AnnotationBean {
public AnnotationBean() {
System.out.println("【构造方法】AnnotationBean被创建");
}
/**
* 初始化方法:在Bean属性赋值完成后调用
* 适合做:资源加载、数据初始化、连接建立等
*/
@PostConstruct
public void init() {
System.out.println("【@PostConstruct】执行初始化逻辑");
// 示例:加载配置文件
loadConfiguration();
// 示例:建立数据库连接
connectDatabase();
}
/**
* 销毁方法:在Bean销毁前调用
* 适合做:资源释放、连接关闭、数据保存等
*/
@PreDestroy
public void cleanup() {
System.out.println("【@PreDestroy】执行清理逻辑");
// 示例:关闭数据库连接
closeDatabase();
// 示例:保存数据
saveData();
}
private void loadConfiguration() {
System.out.println(" - 加载配置文件");
}
private void connectDatabase() {
System.out.println(" - 建立数据库连接");
}
private void closeDatabase() {
System.out.println(" - 关闭数据库连接");
}
private void saveData() {
System.out.println(" - 保存数据");
}
}
6.3 方式2:实现InitializingBean和DisposableBean接口
java
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
* 实现Spring提供的接口
* 缺点:代码与Spring框架耦合
*/
@Component
public class InterfaceBean implements InitializingBean, DisposableBean {
public InterfaceBean() {
System.out.println("【构造方法】InterfaceBean被创建");
}
/**
* InitializingBean接口的方法
* 在属性设置完成后调用
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【InitializingBean】afterPropertiesSet方法执行");
// 执行初始化逻辑
}
/**
* DisposableBean接口的方法
* 在Bean销毁时调用
*/
@Override
public void destroy() throws Exception {
System.out.println("【DisposableBean】destroy方法执行");
// 执行清理逻辑
}
}
6.4 方式3:使用init-method和destroy-method
java
// Bean类
public class XmlBean {
public XmlBean() {
System.out.println("【构造方法】XmlBean被创建");
}
// 自定义初始化方法(方法名可以自定义)
public void customInit() {
System.out.println("【init-method】customInit方法执行");
}
// 自定义销毁方法(方法名可以自定义)
public void customDestroy() {
System.out.println("【destroy-method】customDestroy方法执行");
}
}
// 配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
/**
* 通过@Bean注解的initMethod和destroyMethod属性指定初始化和销毁方法
*/
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public XmlBean xmlBean() {
return new XmlBean();
}
}
6.5 三种方式的混合使用
如果同时使用多种方式,执行顺序如下:
java
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class MixedBean implements InitializingBean, DisposableBean {
public MixedBean() {
System.out.println("0. 构造方法执行");
}
@PostConstruct
public void postConstruct() {
System.out.println("1. @PostConstruct执行");
}
@Override
public void afterPropertiesSet() {
System.out.println("2. InitializingBean.afterPropertiesSet执行");
}
public void customInit() {
System.out.println("3. init-method执行");
}
@PreDestroy
public void preDestroy() {
System.out.println("4. @PreDestroy执行");
}
@Override
public void destroy() {
System.out.println("5. DisposableBean.destroy执行");
}
public void customDestroy() {
System.out.println("6. destroy-method执行");
}
}
@Configuration
class MixedBeanConfig {
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public MixedBean mixedBean() {
return new MixedBean();
}
}
/* 运行结果:
* 0. 构造方法执行
* 1. @PostConstruct执行
* 2. InitializingBean.afterPropertiesSet执行
* 3. init-method执行
* (Bean使用中...)
* 4. @PreDestroy执行
* 5. DisposableBean.destroy执行
* 6. destroy-method执行
*/
6.6 实际应用场景
场景1:数据库连接池初始化
java
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class DatabaseConnectionPool {
@Value("${db.url}")
private String dbUrl;
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
private HikariDataSource dataSource;
/**
* 初始化:创建数据库连接池
*/
@PostConstruct
public void init() {
System.out.println("【初始化】创建数据库连接池");
dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dbUrl);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setMaximumPoolSize(10); // 最大连接数
System.out.println("【初始化完成】数据库连接池已就绪");
}
/**
* 销毁:关闭数据库连接池
*/
@PreDestroy
public void cleanup() {
System.out.println("【清理】关闭数据库连接池");
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
System.out.println("【清理完成】数据库连接池已关闭");
}
public HikariDataSource getDataSource() {
return dataSource;
}
}
场景2:缓存预加载
java
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Component
public class CacheManager {
// 模拟缓存
private Map<String, Object> cache = new HashMap<>();
/**
* 初始化:预加载热点数据到缓存
*/
@PostConstruct
public void preloadCache() {
System.out.println("【初始化】开始预加载缓存数据");
// 模拟从数据库加载热点数据
cache.put("hotData1", "这是热点数据1");
cache.put("hotData2", "这是热点数据2");
cache.put("hotData3", "这是热点数据3");
System.out.println("【初始化完成】已加载 " + cache.size() + " 条缓存数据");
}
public Object get(String key) {
return cache.get(key);
}
}
场景3:定时任务启动和停止
java
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Component
public class ScheduledTaskManager {
private ScheduledExecutorService scheduler;
/**
* 初始化:启动定时任务
*/
@PostConstruct
public void startScheduledTask() {
System.out.println("【初始化】启动定时任务");
scheduler = Executors.newScheduledThreadPool(1);
// 每10秒执行一次任务
scheduler.scheduleAtFixedRate(() -> {
System.out.println("【定时任务】执行清理操作:" + System.currentTimeMillis());
}, 0, 10, TimeUnit.SECONDS);
System.out.println("【初始化完成】定时任务已启动");
}
/**
* 销毁:停止定时任务
*/
@PreDestroy
public void stopScheduledTask() {
System.out.println("【清理】停止定时任务");
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown();
try {
// 等待任务完成,最多等待5秒
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow(); // 强制停止
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
}
}
System.out.println("【清理完成】定时任务已停止");
}
}
7. 完整代码示例
7.1 演示所有生命周期阶段的完整Bean
java
package com.example.demo.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* 完整的Bean生命周期演示
* 这个Bean实现了所有的生命周期接口和回调方法
*
* @author Demo
*/
@Component // 标记为Spring组件,由容器管理
public class CompleteLifeCycleBean implements
BeanNameAware, // 获取Bean名称
BeanFactoryAware, // 获取BeanFactory
ApplicationContextAware, // 获取ApplicationContext
InitializingBean, // 初始化Bean
DisposableBean { // 销毁Bean
// ========== 属性 ==========
@Value("${user.name:DefaultUser}") // 从配置文件读取,默认值为DefaultUser
private String userName;
@Autowired(required = false) // 自动注入(可选)
private UserService userService;
// 用于保存Bean名称
private String beanName;
// ========== 第1阶段:实例化 ==========
/**
* 构造方法:Bean被创建的第一步
* 此时还没有进行属性注入,所以userName和userService都是null
*/
public CompleteLifeCycleBean() {
System.out.println("\n========== Bean生命周期开始 ==========");
System.out.println("【阶段1-构造方法】Bean对象被创建");
System.out.println(" 当前userName的值: " + userName); // null
System.out.println(" 当前userService的值: " + userService); // null
}
// ========== 第2阶段:属性赋值 ==========
// Spring自动完成,不需要我们写代码
// 这个阶段会给userName和userService赋值
// ========== 第3阶段:BeanNameAware接口 ==========
/**
* 设置Bean的名字
* Spring会调用这个方法,告诉Bean它在容器中的名字
*/
@Override
public void setBeanName(String name) {
System.out.println("\n【阶段3-BeanNameAware】设置Bean名称");
System.out.println(" Bean的名字是: " + name);
this.beanName = name;
}
// ========== 第4阶段:BeanFactoryAware接口 ==========
/**
* 设置BeanFactory
* 通过这个接口,Bean可以获取到创建它的工厂
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("\n【阶段4-BeanFactoryAware】设置BeanFactory");
System.out.println(" BeanFactory类型: " + beanFactory.getClass().getSimpleName());
// 可以使用beanFactory获取其他Bean
}
// ========== 第5阶段:ApplicationContextAware接口 ==========
/**
* 设置ApplicationContext
* 这是最强大的Aware接口,可以获取应用上下文的所有功能
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("\n【阶段5-ApplicationContextAware】设置ApplicationContext");
System.out.println(" ApplicationContext类型: " + applicationContext.getClass().getSimpleName());
System.out.println(" 容器中的Bean数量: " + applicationContext.getBeanDefinitionCount());
}
// ========== 第6阶段:BeanPostProcessor前置处理 ==========
// 这个方法在MyBeanPostProcessor类中实现
// ========== 第7阶段:@PostConstruct注解 ==========
/**
* 初始化方法1:@PostConstruct注解
* 这是推荐的初始化方法,因为它是JSR-250标准,不依赖Spring
* 此时属性已经注入完成,可以安全使用
*/
@PostConstruct
public void postConstructMethod() {
System.out.println("\n【阶段7-@PostConstruct】执行初始化方法");
System.out.println(" 此时userName的值: " + userName); // 已经有值了
System.out.println(" 执行初始化逻辑,例如:加载配置、建立连接等");
}
// ========== 第8阶段:InitializingBean接口 ==========
/**
* 初始化方法2:InitializingBean接口的afterPropertiesSet方法
* 这个方法在@PostConstruct之后执行
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("\n【阶段8-InitializingBean】afterPropertiesSet方法执行");
System.out.println(" 执行初始化逻辑");
}
// ========== 第9阶段:自定义init-method ==========
// 在配置类中通过@Bean(initMethod="customInit")指定
/**
* 初始化方法3:自定义初始化方法
* 需要在@Bean注解中通过initMethod属性指定
*/
public void customInit() {
System.out.println("\n【阶段9-自定义init-method】customInit方法执行");
System.out.println(" 执行初始化逻辑");
}
// ========== 第10阶段:BeanPostProcessor后置处理 ==========
// 这个方法在MyBeanPostProcessor类中实现
// ========== Bean可以使用了!==========
/**
* 业务方法:Bean初始化完成后,可以执行正常的业务逻辑
*/
public void doSomething() {
System.out.println("\n【Bean使用中】执行业务方法");
System.out.println(" Bean名称: " + beanName);
System.out.println(" 用户名: " + userName);
}
// ========== 销毁阶段开始 ==========
// ========== 第11阶段:@PreDestroy注解 ==========
/**
* 销毁方法1:@PreDestroy注解
* 这是推荐的销毁方法,因为它是JSR-250标准
* 在容器关闭时自动调用
*/
@PreDestroy
public void preDestroyMethod() {
System.out.println("\n========== Bean销毁阶段开始 ==========");
System.out.println("【阶段11-@PreDestroy】执行销毁前的清理工作");
System.out.println(" 例如:关闭连接、释放资源、保存数据等");
}
// ========== 第12阶段:DisposableBean接口 ==========
/**
* 销毁方法2:DisposableBean接口的destroy方法
* 在@PreDestroy之后执行
*/
@Override
public void destroy() throws Exception {
System.out.println("\n【阶段12-DisposableBean】destroy方法执行");
System.out.println(" 执行清理逻辑");
}
// ========== 第13阶段:自定义destroy-method ==========
// 在配置类中通过@Bean(destroyMethod="customDestroy")指定
/**
* 销毁方法3:自定义销毁方法
* 需要在@Bean注解中通过destroyMethod属性指定
*/
public void customDestroy() {
System.out.println("\n【阶段13-自定义destroy-method】customDestroy方法执行");
System.out.println(" 执行清理逻辑");
System.out.println("========== Bean生命周期结束 ==========\n");
}
}
// ========== 模拟的UserService ==========
@Component
class UserService {
public UserService() {
System.out.println(" [依赖Bean] UserService被创建");
}
}
7.2 BeanPostProcessor实现类
java
package com.example.demo.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* Bean后置处理器
* 会对容器中的所有Bean生效
*
* @author Demo
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 初始化之前调用
* 在@PostConstruct之前执行
*
* @param bean Bean实例
* @param beanName Bean名称
* @return 返回的Bean(可以是原始Bean,也可以是包装后的Bean)
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 只处理我们关心的Bean,避免影响其他Bean
if (bean instanceof CompleteLifeCycleBean) {
System.out.println("\n【阶段6-BeanPostProcessor前置】处理Bean: " + beanName);
System.out.println(" 可以在这里修改Bean或返回代理对象");
}
return bean; // 返回原始Bean或修改后的Bean
}
/**
* 初始化之后调用
* 在所有初始化方法(@PostConstruct、afterPropertiesSet、init-method)之后执行
*
* @param bean Bean实例
* @param beanName Bean名称
* @return 返回的Bean(可以是原始Bean,也可以是代理对象)
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 只处理我们关心的Bean
if (bean instanceof CompleteLifeCycleBean) {
System.out.println("\n【阶段10-BeanPostProcessor后置】处理Bean: " + beanName);
System.out.println(" 可以在这里返回代理对象(AOP就是在这里实现的)");
System.out.println("\n========== Bean初始化完成,可以使用了!==========");
}
return bean; // 返回原始Bean或代理对象
}
}
7.3 测试类
java
package com.example.demo;
import com.example.demo.bean.CompleteLifeCycleBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 测试类:演示Bean的完整生命周期
*
* @author Demo
*/
@Configuration
@ComponentScan("com.example.demo") // 扫描包
public class BeanLifeCycleTest {
public static void main(String[] args) {
System.out.println("==================== 开始启动Spring容器 ====================\n");
// 1. 创建Spring容器
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(BeanLifeCycleTest.class);
System.out.println("\n==================== Spring容器启动完成 ====================");
// 2. 从容器中获取Bean
CompleteLifeCycleBean bean = context.getBean(CompleteLifeCycleBean.class);
// 3. 使用Bean
bean.doSomething();
System.out.println("\n==================== 准备关闭Spring容器 ====================");
// 4. 关闭容器(会触发Bean的销毁)
context.close();
System.out.println("\n==================== Spring容器已关闭 ====================");
}
}
8. 常见面试问题及答案 💡
8.1 基础问题
❓ Q1:什么是Spring Bean?
答案:
Spring Bean是由Spring IoC容器管理的对象。具有以下特点:
- 由容器创建:不需要程序员手动new对象
- 由容器管理:生命周期由Spring管理
- 支持依赖注入:自动注入所需依赖
- 支持AOP:可方便地应用切面编程
java
// 传统方式
UserService userService = new UserService();
// Spring方式
@Component
public class UserService { }
❓ Q2:Bean是否单例?单例和原型的区别?
答案 :是的,默认是单例(Singleton)
对比表:
| 对比项 | Singleton | Prototype |
|---|---|---|
| 创建时机 | 容器启动时 | getBean时 |
| 实例数量 | 一个 | 多个 |
| 销毁管理 | 容器销毁 | 不销毁 |
| 线程安全 | 需注意 | 天然安全 |
❓ Q3:Bean生命周期有哪些阶段?
答案 :13个阶段
记忆口诀:创建→赋值→感知→前置→初始化→后置→使用→销毁
1-2. 实例化、属性赋值
3-5. Aware接口回调
- BeanPostProcessor前置
7-9. 三种初始化方法
- BeanPostProcessor后置
11-13. 三种销毁方法
❓ Q4:Prototype Bean为什么不会被销毁?
答案:
Spring对Prototype Bean只负责创建,不负责销毁。
原因:
- Prototype Bean可能有多个实例
- 容器无法追踪所有实例
- 生命周期由使用者管理
手动销毁:
java
@Component
@Scope("prototype")
public class PrototypeBean implements DisposableBean {
public void destroy() {
// 清理资源
}
}
// 使用时手动调用
bean.destroy();
8.2 进阶问题
❓ Q5:Spring Bean有哪些作用域?
答案 :6种作用域
| 作用域 | 说明 | 适用 |
|---|---|---|
| singleton | 单例(默认) | 所有 |
| prototype | 每次新建 | 所有 |
| request | 每请求一个 | Web |
| session | 每会话一个 | Web |
| application | 每应用一个 | Web |
| websocket | 每连接一个 | WS |
❓ Q6:如何在Bean加载/销毁时执行自定义逻辑?
答案 :3种方式
方式1(推荐):
java
@Component
public class MyBean {
@PostConstruct
public void init() { }
@PreDestroy
public void cleanup() { }
}
方式2:
java
public class MyBean implements InitializingBean, DisposableBean {
public void afterPropertiesSet() { }
public void destroy() { }
}
方式3:
java
@Bean(initMethod = "init", destroyMethod = "cleanup")
public MyBean myBean() { return new MyBean(); }
执行顺序:@PostConstruct → InitializingBean → init-method
❓ Q7:什么是BeanPostProcessor?
答案:
Bean后置处理器,在Bean初始化前后进行处理。
作用:
- 修改Bean属性
- 返回代理对象(AOP实现原理)
- 添加日志、监控等
java
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String name) {
// 初始化前处理
return bean;
}
public Object postProcessAfterInitialization(Object bean, String name) {
// 初始化后处理(AOP在这里返回代理)
return bean;
}
}
❓ Q8:Aware接口有什么作用?
答案:
让Bean感知Spring容器,获取容器资源。
常用Aware接口:
| 接口 | 作用 |
|---|---|
| BeanNameAware | 获取Bean名称 |
| BeanFactoryAware | 获取BeanFactory |
| ApplicationContextAware | 获取ApplicationContext |
| EnvironmentAware | 获取环境变量 |
java
@Component
public class MyBean implements ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext ctx) {
this.context = ctx;
}
}
8.3 高级问题
❓ Q9:Spring如何解决循环依赖?
答案:
通过三级缓存解决单例Bean的循环依赖。
三级缓存:
- 一级缓存(singletonObjects):完整Bean
- 二级缓存(earlySingletonObjects):早期Bean
- 三级缓存(singletonFactories):Bean工厂
解决过程:
1. 创建A,放入三级缓存
2. A注入B,开始创建B
3. B注入A,从三级缓存获取A(半成品)
4. B初始化完成,放入一级缓存
5. A获得B,完成初始化,放入一级缓存
限制:
- ❌ 无法解决:Prototype作用域、构造器注入
- ✅ 可以解决:Singleton的属性注入
解决方案:
java
@Lazy // 延迟加载
@Autowired
private BeanB beanB;
❓ Q10:如何保证单例Bean线程安全?
答案:
5种方案:
1. 无状态Bean(推荐)
java
@Component
public class SafeBean {
// 没有成员变量,天然线程安全
public int add(int a, int b) {
return a + b;
}
}
2. ThreadLocal
java
private ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> 0);
3. synchronized
java
public synchronized void increment() { count++; }
4. 原子类
java
private AtomicInteger count = new AtomicInteger(0);
5. Prototype作用域
java
@Scope("prototype")
最佳实践:
- Service/DAO层:无状态单例
- Controller层:不用成员变量存请求数据
- 有状态Bean:使用Prototype或ThreadLocal
9. 总结 📝
9.1 核心要点
✅ Bean是Spring IoC容器管理的对象,默认单例
✅ 生命周期13个阶段:创建→赋值→感知→前置→初始化→后置→使用→销毁
✅ 推荐使用@PostConstruct/@PreDestroy做初始化/销毁
✅ Prototype Bean不会被容器销毁,需手动管理
✅ 单例Bean要注意线程安全,优先设计无状态
9.2 最佳实践
- 初始化/销毁:优先@PostConstruct/@PreDestroy
- Service层:无状态单例Bean
- 有状态Bean:Prototype作用域
- 避免循环依赖:使用@Lazy或重新设计
- 线程安全:无状态设计或ThreadLocal
9.3 常见陷阱
⚠️ Prototype Bean不会调用销毁方法
⚠️ 单例Bean的成员变量线程不安全
⚠️ 构造器注入的循环依赖无法解决
⚠️ 构造方法中依赖尚未注入
📚 参考资料
🎉 恭喜你完成学习!
通过本文,你已经掌握了Spring Bean生命周期的完整知识体系,包括基础概念、详细流程、实战代码和面试要点。
如果这篇文章对你有帮助,请点赞👍、收藏⭐、分享📤!