当前位置: 首页 > news >正文

一文讲透 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):真正执行业务逻辑的对象。

  • 代理对象(Proxy):Spring 创建的包装对象,负责在调用目标方法前后执行通知。

下面是一个简单的日志切面:

@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 线程不安全而出现的”,原因有三点:

  1. 它解决的是实例创建和状态隔离问题,不保证 Bean 内部使用的数据库、静态变量等共享资源安全;

  2. Spring 创建 prototype Bean 后通常不再负责其完整销毁流程;

  3. prototype Bean 直接注入 singleton Bean 时,依赖通常只在单例创建时解析一次,之后可能一直使用同一个 prototype 实例。

如果单例每次调用都需要新的 prototype 实例,应使用ObjectProvider、方法注入或作用域代理。更推荐的设计仍是让 Service 保持无状态,将请求数据放在参数和局部变量中;必要时再使用原子类、并发容器或锁。

五、Spring 事务与 ACID

Spring 通过PlatformTransactionManager统一抽象 JDBC、JPA 等事务技术,并提供编程式事务和声明式事务。实际开发最常见的是使用@Transactional的声明式事务。

数据库事务具有 ACID 特性:

  • 原子性(Atomicity):事务中的操作全部成功或全部回滚;

  • 一致性(Consistency):事务执行前后数据满足业务约束;

  • 隔离性(Isolation):并发事务之间相互隔离;

  • 持久性(Durability):事务提交后结果能够持久保存。

Spring 本身不是数据库,不直接实现 ACID。它负责划定事务边界、选择事务管理器并调用提交或回滚,真正的 ACID 主要由数据库事务机制保障。

@Transactional的底层也是 AOP。外部调用事务代理时,事务拦截器先开启或加入事务,再执行目标方法;方法正常结束就提交,出现符合规则的异常则回滚。默认情况下,未捕获的RuntimeExceptionError会触发回滚,受检异常默认不会。

六、A 调用 B,B 的事务会生效吗

假设同一个类中存在两个方法:

@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 的注解通过自调用生效,而是外层事务本来就存在。

七、如何解决事务自调用

常见方案有三种:

  1. 拆分类职责:把 B 移到另一个 Spring Bean,由 A 注入并调用它。这是最清晰、最推荐的方式。

  2. 通过代理调用:注入自身代理或从容器获取代理后调用,但会增加代码与容器的耦合,应谨慎使用。

  3. 使用 AspectJ 模式:通过编译期或加载期织入直接修改字节码,不依赖代理调用,因此能处理自调用,但配置更复杂。

也可以将@Transactional放到由外部调用的 A 上,让 A 统一作为事务边界,但要确认这符合业务原子性和传播要求。

八、总结

Spring AOP 通过切点选中方法,通过通知定义增强逻辑,并以 JDK 动态代理或 CGLIB 创建代理对象。Bean 作用域决定实例的创建和共享范围,prototype适合有状态对象,但不能代替正确的并发设计。Spring 声明式事务是 AOP 的典型应用,数据库负责 ACID,Spring 负责事务边界和提交回滚。

参考资料:Spring AOP 概念、Spring Bean 作用域、Spring 事务注解。

http://www.jsqmd.com/news/1400893/

相关文章:

  • 基于OpenClaw与Playwright的小红书自动化发文系统构建指南
  • OpenAI服务额度管理:从付费重置到高效使用策略
  • Ubuntu国内镜像源配置全攻略:APT换源原理与实战优化
  • Java Web调试端口冲突:从JPDA原理到Tomcat/WildFly实战解决
  • Windows 10家庭版MySQL 8.0安装初始化无响应问题深度排查与实战部署指南
  • 基于Tampermonkey的网页视频自动连播脚本开发实战
  • 图标设计尺寸与栅格模板指南
  • 一款MIT协议开源-免费任意商用的在线绘制流程图工具
  • 大语言模型智能体效率优化:GitHub脚本化技能实战
  • 手把手教你学 Simulink—— 航空电机泵用多级离心泵电机的轴向力平衡控制仿真
  • Win10深色模式全攻略:从护眼原理到自动切换与排错
  • 2026广东港口与航道监理乙级资质代办公司实力观察:专业度与效率的双重考量 - 卓企推荐
  • 阿里云Model Studio上下文缓存:大模型API调用降本增效实战指南
  • 信号量与互斥体对zynqMP的ps_led的互斥访问
  • 遗传算法优化电力交易决策:Python实现与工程实践
  • 金融级AI应用实战:从零构建可复现的信用评分预测服务
  • CentOS6.5 虚拟机部署 iSCSI 存储 + multipath 多路径完整实操
  • 01-03-运行时-类型加载器-从IL元数据到运行时类型
  • 生产环境 Agent 人工审批环节:从 0 到 1 技术实现与用户操作指南
  • 线程池如何调优?
  • MySQL、Oracle、SQL Server查询当前时间函数全解析与跨数据库实践
  • Windows CMD FOR命令深度解析:从基础循环到自动化脚本引擎
  • 构建个人数据库知识库:从原理到实战的十万字笔记方法论
  • 经典PID与模糊PID控制:原理对比、仿真实现与工程选型指南
  • 武汉考研机构推荐2026哪家好 - 弘毅考研
  • PTA团体程序设计天梯赛L1真题讲解L1-077-080
  • Nginx安全配置实战:从基础代理到多层拉黑策略
  • windows 驱动实例分析系列: wintun驱动分析-api篇(二)
  • NumPy数组创建全解析:empty、zeros、ones与full函数性能与应用指南
  • 群晖NAS通过虚拟机稳定同步115网盘:架构、部署与优化指南