一、Bean 的生命周期(通俗版)
想象 Bean 的一生就像一个人的成长过程:
-
出生阶段
-
Instantiation
(实例化):相当于"怀孕"(new
出对象) -
Populate properties
(属性注入):相当于"喂奶"(@Autowired
注入依赖) -
BeanNameAware.setBeanName()
:起名字(知道自己的Bean ID) -
BeanFactoryAware.setBeanFactory()
:认识工厂(知道谁管理自己)
-
-
成长阶段
-
BeanPostProcessor.postProcessBeforeInitialization()
:上学前的体检 -
@PostConstruct
:成年礼(自定义初始化) -
InitializingBean.afterPropertiesSet()
:毕业典礼(Spring提供的初始化) -
BeanPostProcessor.postProcessAfterInitialization()
:入职培训
-
-
工作阶段
-
正常提供服务(业务方法被调用)
-
-
死亡阶段
-
@PreDestroy
:写遗嘱(自定义销毁) -
DisposableBean.destroy()
:火化(Spring提供的销毁)
-
二、Spring 支持的 Bean 作用域
作用域 | 说明 | 适用场景 |
---|---|---|
singleton | 默认,整个容器只有一个实例 | 无状态的Service、DAO |
prototype | 每次获取都创建新实例 | 有状态的Bean(如购物车) |
request | 每个HTTP请求一个实例(Web环境) | 用户登录信息 |
session | 每个HTTP会话一个实例(Web环境) | 购物车数据 |
application | 整个Web应用共享一个实例(Web环境) | 全局配置 |
websocket | 每个WebSocket会话一个实例 | 实时通信场景 |
配置方式:
@Scope("prototype") // 类或@Bean方法上使用
@Component
public class ShoppingCart {}
三、Bean 的扩展点(关键钩子)
扩展点 | 作用时机 | 典型用途 |
---|---|---|
BeanPostProcessor | 初始化前后 | AOP代理、监控 |
BeanFactoryPostProcessor | Bean定义加载后,实例化前 | 修改Bean定义(如属性占位符) |
InstantiationAwareBeanPostProcessor | 实例化前后 | 自定义实例化逻辑 |
@PostConstruct | 属性注入后,初始化前 | 数据校验、资源初始化 |
@PreDestroy | Bean销毁前 | 资源释放 |
示例:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("初始化前处理: " + beanName);
return bean;
}
}
四、Spring 事务的实现方式
1. 编程式事务(手动控制)
@Autowired
private TransactionTemplate transactionTemplate;
public void transfer() {
transactionTemplate.execute(status -> {
try {
accountDao.deduct(from, amount);
accountDao.add(to, amount);
return true;
} catch (Exception e) {
status.setRollbackOnly(); // 回滚
return false;
}
});
}
2. 声明式事务(推荐,基于AOP)
@Transactional
public void transfer(Long from, Long to, BigDecimal amount) {
accountDao.deduct(from, amount);
accountDao.add(to, amount);
}
实现原理:
通过 TransactionInterceptor
拦截 @Transactional
方法,利用动态代理管理事务
五、Spring 事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
---|---|---|---|---|
DEFAULT | ❌ | ❌ | ❌ | 使用数据库默认设置 |
READ_UNCOMMITTED | ✔️ | ✔️ | ✔️ | 几乎不用 |
READ_COMMITTED | ❌ | ✔️ | ✔️ | 最常用(Oracle默认) |
REPEATABLE_READ | ❌ | ❌ | ✔️ | MySQL默认 |
SERIALIZABLE | ❌ | ❌ | ❌ | 严格一致性,性能差 |
配置方式:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateData() {
// ...
}
六、关键面试问题
-
Bean 生命周期中哪些阶段可以修改Bean?
-
BeanPostProcessor
可以在初始化前后修改Bean -
BeanFactoryPostProcessor
可以修改Bean的定义
-
-
为什么需要原型作用域?
-
当需要每次使用新对象时(如购物车、计数器)
-
-
事务失效的常见原因?
-
方法非public
-
同类内方法调用(未经过代理)
-
异常被捕获未抛出
-
数据库引擎不支持(如MyISAM)
-
Spring事务失效场景、传播特性与线程安全问题详解
一、Spring事务失效的8大常见场景
1. 方法非public修饰
@Transactional // ❌ 失效
private void updateOrder() {
// 事务不生效
}
原因:Spring AOP代理要求目标方法必须是public
2. 同类内方法调用
@Service
public class OrderService {
public void createOrder() {
this.updateOrder(); // ❌ 事务失效(直接调用了this)
}
@Transactional
public void updateOrder() {
// 不会被事务增强
}
}
解决方案:
-
方法拆分到不同类
-
通过
AopContext.currentProxy()
获取代理对象调用
3. 异常被捕获未抛出
@Transactional
public void process() {
try {
// 数据库操作
} catch (Exception e) {
// ❌ 捕获异常未抛出
log.error("错误", e);
}
}
正确做法:
@Transactional(rollbackFor = Exception.class)
public void process() throws Exception {
try {
// 操作
} catch (Exception e) {
log.error("错误", e);
throw e; // ✅ 重新抛出
}
}
4. 数据库引擎不支持
-
MyISAM引擎不支持事务(需改用InnoDB)
5. 未启用事务管理
// 忘记加注解
// @EnableTransactionManagement
@SpringBootApplication
public class App {}
6. 错误的事务管理器配置
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource); // 必须配置正确
}
7. 多数据源未指定事务管理器
@Transactional(transactionManager = "orderTransactionManager") // 需明确指定
public void multiDataSourceOp() {}
8. @Transactional注解在接口上
public interface UserService {
@Transactional // ❌ JDK动态代理可能失效
void updateUser();
}
建议:注解写在实现类上
二、Spring事务传播特性(7种)
传播行为类型 | 说明 | 适用场景 |
---|---|---|
REQUIRED(默认) | 如果当前没有事务,就新建一个;如果已存在,则加入 | 大多数业务方法 |
SUPPORTS | 支持当前事务,如果没有则以非事务方式执行 | 查询方法 |
MANDATORY | 必须运行在事务中,否则抛出异常 | 强制要求事务的场景 |
REQUIRES_NEW | 新建事务,如果当前存在事务则挂起 | 日志记录(需独立提交) |
NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务则挂起 | 发送消息(不需要事务) |
NEVER | 以非事务方式执行,如果当前存在事务则抛出异常 | 禁止事务的场景 |
NESTED | 如果当前存在事务,则在嵌套事务内执行(可部分回滚) | 复杂业务中的子操作 |
代码示例:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation() {
// 始终在新事务中执行
}
三、Spring单例Bean的线程安全问题
1. 为什么会有线程安全问题?
-
单例Bean默认是线程不安全的(所有线程共享同一实例)
-
如果Bean包含可变状态(成员变量),可能引发并发问题
2. 典型不安全场景
@Service // 单例
public class CounterService {
private int count = 0; // ❌ 共享变量
public void increment() {
count++; // 线程不安全
}
}
3. 解决方案
(1) 无状态设计(推荐)
@Service
public class SafeService {
// ✅ 不保存状态,只使用局部变量和参数
public int process(int input) {
int temp = input + 1; // 线程安全
return temp;
}
}
(2) 使用ThreadLocal
@Service
public class UserContext {
private ThreadLocal<User> currentUser = new ThreadLocal<>();
public void setUser(User user) {
currentUser.set(user); // 线程隔离
}
}
(3) 改为原型作用域
@Scope("prototype") // 每次请求新实例
@Service
public class PrototypeService {
private int count;
}
(4) 同步控制(影响性能)
@Service
public class SyncService {
private final Object lock = new Object();
private int count;
public void safeIncrement() {
synchronized(lock) { // 加锁
count++;
}
}
}
4. 常见线程安全组件
-
Controller:通常无状态(但注意注入的Service是否安全)
-
Service:需确保无状态或使用ThreadLocal
-
DAO/Repository:MyBatis Mapper等本身就是线程安全的
四、总结对比表
问题领域 | 关键点 |
---|---|
事务失效 | 检查方法可见性、异常处理、同类调用、引擎支持等 |
传播特性 | 根据业务需求选择REQUIRED/REQUIRES_NEW/NESTED等 |
单例线程安全 | 优先无状态设计,必要时用ThreadLocal或原型作用域 |
最佳实践:
事务方法保持简短,避免长时间占用连接
单例Service避免使用成员变量存储状态
复杂事务用
REQUIRES_NEW
拆分