一文讲透 Spring AOP、Bean 作用域与事务:从代理机制到事务失效

📅 发布时间:2026/8/15 22:28:15
一文讲透 Spring AOP、Bean 作用域与事务:从代理机制到事务失效 Spring AOP、Bean 作用域和事务看似是三个知识点实际上都与 Spring 容器和代理机制密切相关容器管理 Bean 的生命周期AOP 为 Bean 创建代理声明式事务则是 AOP 的典型应用。一、Spring AOP 是什么AOP即面向切面编程用于抽取日志、权限、事务、监控等分散在多个业务类中的横切逻辑使业务方法只关注自身功能。Spring AOP 的核心概念如下连接点Join Point程序执行过程中可以被增强的位置。在 Spring AOP 中主要表示方法执行。切点Pointcut用于筛选连接点决定哪些方法需要增强。通知Advice切点命中后要执行的动作包括Before、After、AfterReturning、AfterThrowing和Around。切面Aspect切点与通知的组合表示“在哪里执行什么增强逻辑”。目标对象Target真正执行业务逻辑的对象。代理对象ProxySpring 创建的包装对象负责在调用目标方法前后执行通知。下面是一个简单的日志切面Aspect Component public class LogAspect { Pointcut(execution(* com.example.service..*(..))) public void serviceMethods() {} Around(serviceMethods()) public Object record(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { System.out.println(耗时 (System.currentTimeMillis() - start)); } } }execution表达式是切点表示匹配service包及其子包中的所有方法Around是环绕通知两者共同组成切面。二、Spring AOP 的具体实现Spring 容器创建 Bean 时自动代理创建器会判断该 Bean 的方法是否被切点匹配。若需要增强Spring 不直接返回原对象而是返回代理对象。调用过程可以简化为调用代理对象 → 匹配切点 → 执行通知链 → 调用目标方法 → 执行后续通知 → 返回结果目标类实现接口时Spring Framework 通常可以使用 JDK 动态代理没有接口时使用 CGLIB 创建目标类的子类也可以通过配置强制使用 CGLIB。由于 CGLIB 依赖继承和方法重写final类、final方法和private方法不能按普通方式被增强。Spring AOP 属于运行时代理只能拦截经过代理对象的方法调用。理解这一点是理解事务自调用失效的关键。三、Spring Bean 的作用域Spring 常用 Bean 作用域包括作用域含义singleton默认值一个容器中同一 Bean 定义共享一个实例prototype每次向容器请求时创建新实例request每个 HTTP 请求一个实例session每个 HTTP Session 一个实例application每个ServletContext一个实例websocket每个 WebSocket 会话一个实例singleton只保证容器内共享同一实例并不保证线程安全。无共享可变状态的 Service 通常可以安全地使用单例如果单例 Bean 保存会被多个线程修改的成员变量就可能产生数据竞争。四、prototype 是否为了解决线程安全prototype每次从容器获取时都会创建新对象适合保存独立状态的 Bean。不同线程如果各自获得不同实例共享状态确实会减少因此它可以用于部分有状态场景。但不能简单地说“prototype 就是为了解决 singleton 线程不安全而出现的”原因有三点它解决的是实例创建和状态隔离问题不保证 Bean 内部使用的数据库、静态变量等共享资源安全Spring 创建 prototype Bean 后通常不再负责其完整销毁流程prototype Bean 直接注入 singleton Bean 时依赖通常只在单例创建时解析一次之后可能一直使用同一个 prototype 实例。如果单例每次调用都需要新的 prototype 实例应使用ObjectProvider、方法注入或作用域代理。更推荐的设计仍是让 Service 保持无状态将请求数据放在参数和局部变量中必要时再使用原子类、并发容器或锁。五、Spring 事务与 ACIDSpring 通过PlatformTransactionManager统一抽象 JDBC、JPA 等事务技术并提供编程式事务和声明式事务。实际开发最常见的是使用Transactional的声明式事务。数据库事务具有 ACID 特性原子性Atomicity事务中的操作全部成功或全部回滚一致性Consistency事务执行前后数据满足业务约束隔离性Isolation并发事务之间相互隔离持久性Durability事务提交后结果能够持久保存。Spring 本身不是数据库不直接实现 ACID。它负责划定事务边界、选择事务管理器并调用提交或回滚真正的 ACID 主要由数据库事务机制保障。Transactional的底层也是 AOP。外部调用事务代理时事务拦截器先开启或加入事务再执行目标方法方法正常结束就提交出现符合规则的异常则回滚。默认情况下未捕获的RuntimeException和Error会触发回滚受检异常默认不会。六、A 调用 BB 的事务会生效吗假设同一个类中存在两个方法Service public class OrderService { public void methodA() { methodB(); } Transactional public void methodB() { // 数据库操作 } }在 Spring 默认代理模式下外部调用代理对象的methodA()代理发现 A 没有事务注解于是直接调用目标对象。A 内部调用 B本质是this.methodB();这次调用发生在目标对象内部没有重新经过 Spring 代理所以 B 上的Transactional通常不会生效。这就是事务的自调用失效。需要注意如果 A 本身已经开启事务并采用默认的REQUIRED传播行为B 中的数据库操作仍会运行在 A 已有的事务里但这不是 B 的注解通过自调用生效而是外层事务本来就存在。七、如何解决事务自调用常见方案有三种拆分类职责把 B 移到另一个 Spring Bean由 A 注入并调用它。这是最清晰、最推荐的方式。通过代理调用注入自身代理或从容器获取代理后调用但会增加代码与容器的耦合应谨慎使用。使用 AspectJ 模式通过编译期或加载期织入直接修改字节码不依赖代理调用因此能处理自调用但配置更复杂。也可以将Transactional放到由外部调用的 A 上让 A 统一作为事务边界但要确认这符合业务原子性和传播要求。八、总结Spring AOP 通过切点选中方法通过通知定义增强逻辑并以 JDK 动态代理或 CGLIB 创建代理对象。Bean 作用域决定实例的创建和共享范围prototype适合有状态对象但不能代替正确的并发设计。Spring 声明式事务是 AOP 的典型应用数据库负责 ACIDSpring 负责事务边界和提交回滚。参考资料Spring AOP 概念、Spring Bean 作用域、Spring 事务注解。