Bean 生命周期、Spring作用域、Bean 扩展点与Spring事务详解

Spring Bean与事务全解析及安全问题

一、Bean 的生命周期(通俗版)

想象 Bean 的一生就像一个人的成长过程:

  1. 出生阶段

    • Instantiation(实例化):相当于"怀孕"(new出对象)

    • Populate properties(属性注入):相当于"喂奶"(@Autowired注入依赖)

    • BeanNameAware.setBeanName():起名字(知道自己的Bean ID)

    • BeanFactoryAware.setBeanFactory():认识工厂(知道谁管理自己)

  2. 成长阶段

    • BeanPostProcessor.postProcessBeforeInitialization():上学前的体检

    • @PostConstruct:成年礼(自定义初始化)

    • InitializingBean.afterPropertiesSet():毕业典礼(Spring提供的初始化)

    • BeanPostProcessor.postProcessAfterInitialization():入职培训

  3. 工作阶段

    • 正常提供服务(业务方法被调用)

  4. 死亡阶段

    • @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代理、监控
BeanFactoryPostProcessorBean定义加载后,实例化前修改Bean定义(如属性占位符)
InstantiationAwareBeanPostProcessor实例化前后自定义实例化逻辑
@PostConstruct属性注入后,初始化前数据校验、资源初始化
@PreDestroyBean销毁前资源释放

示例

@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() {
    // ...
}

六、关键面试问题

  1. Bean 生命周期中哪些阶段可以修改Bean?

    • BeanPostProcessor 可以在初始化前后修改Bean

    • BeanFactoryPostProcessor 可以修改Bean的定义

  2. 为什么需要原型作用域?

    • 当需要每次使用新对象时(如购物车、计数器)

  3. 事务失效的常见原因?

    • 方法非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拆分

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值