news 2026/10/22 12:54:23

代理模式与@Transactional失效场景详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
代理模式与@Transactional失效场景详解

0.代理模式演示代码

代理模式相关演示代码:
github:学习Java代理模式的小项目-链接

1. 为什么需要代理模式

1.1 核心问题

在实际开发中,我们经常需要在方法执行的前后做一些额外的操作,比如:

  • 事务管理:方法中往订单表插入数据,如果订单商品表插入失败,需要回滚订单表,整个过程需要事务
  • 异步执行:有些方法需要异步提交到线程池执行
  • 日志记录:在方法前后记录日志
  • 权限校验:方法执行前校验权限

核心诉求:不修改原始类代码,在方法调用的前后自动增强功能。

1.2 代理模式的思想

让代理对象帮我们在方法调用的前后做一些事情:

  • 程序员只负责编写业务方法
  • 代理对象负责在方法前后添加额外逻辑(开启事务、提交事务、异步提交等)
  • 不需要更改原始类的一行代码

2. 静态代理

2.1 方式一:通过接口实现

适用场景:被代理的类实现了接口。

实现方式:

  1. 定义接口OrderService,包含createOrder()方法
  2. 实现类OrderServiceImpl实现createOrder()
  3. 创建代理类OrderServiceProxy也实现OrderService接口
  4. 代理类中持有OrderServiceImpl对象
  5. 在代理类的createOrder()中,调用OrderServiceImpl的方法,并在前后添加增强逻辑

优点:不修改OrderServiceImpl的任何代码。

2.2 方式二:通过子类实现

适用场景:被代理的类没有实现接口。

实现方式:

  1. 创建被代理类OrderService的子类
  2. 通过super调用父类的createOrder()方法
  3. 在调用前后添加增强操作

优点:同样不修改OrderService的代码。

2.3 静态代理的局限

以上两种方式都叫静态代理——需要手动编写代理类或子类。

问题:每个类都要手写一个代理类,太麻烦了。有没有方案可以不写代码,动态地生成代理类?


3. 动态代理

3.1 JDK 动态代理

原理:动态生成接口的实现类,帮你 new 出代理对象。

特点:

  • 本质是动态生成了一个接口的实现类
  • 调用代理对象的方法,就能直接增强原来的类和方法
  • 写一个newProxy()方法传入泛型,可以动态创建任何类的代理对象
  • 要求被代理类必须实现接口

使用方式:

// 任何实现了接口的类,都可以通过 newProxy 获取代理对象OrderServiceproxy=newProxy(OrderService.class);// 调用代理对象的方法,自动在前后增强proxy.createOrder();

3.2 CGLIB 动态代理

原理:动态生成被代理类的子类,帮你 new 出代理对象。

特点:

  • 通过继承(生成子类)来实现的动态代理
  • 不要求被代理类实现接口
  • 如果一个类被final修饰,则不能使用 CGLIB(因为无法继承)
  • 如果一个方法被final修饰,也无法通过 CGLIB 代理增强

3.3 两种动态代理对比

特性JDK 动态代理CGLIB 动态代理
原理动态生成接口实现类动态生成子类
要求被代理类必须实现接口不需要接口
final 类限制不受影响不能代理 final 类
final 方法限制不受影响不能代理 final 方法

4. AOP与Spring的@Transactional

4.1 AOP 的本质

所谓AOP(面向切面编程),其实就是 Spring 在运行时:

  1. 动态使用 CGLIB 或 JDK 动态代理
  2. 帮你动态创建一个代理对象
  3. 你只需要加一个@Transactional注解,它就可以自动帮你开启和提交事务

4.2 @Transactional 的工作原理

  • Spring 在运行时为带有@Transactional注解的类创建代理对象
  • 调用代理对象的方法时,自动在方法前后开启和提交事务
  • 可以通过自定义注解控制哪些方法需要事务增强
  • 增强时判断方法是否有指定注解,有则增强,无则跳过

这样就把事务操作完全封装起来了,程序员只关注业务逻辑。


5. 事务失效的常见场景

5.1 this 调用导致事务失效

场景:在 Service 实现类中,通过this调用另一个带@Transactional的方法。

原因:

  • this指向的是被代理类本身,而不是代理对象
  • 只有通过代理对象调用方法,才能触发方法增强
  • 直接用this调用,绕过了代理,事务不会生效

解决:在 Service 实现类中必须获取代理对象来调用,而不是用this。

5.2 final 方法导致事务失效

场景:将@Transactional注解放在final修饰的方法上。

原因:

  • Spring 默认使用 CGLIB 创建代理(通过继承重写方法实现)
  • final方法不能被重写
  • 所以代理对象无法对该方法进行增强
  • 事务自然失效

5.3 具体失效场景

  • 事务方法不是public的(spring无法代理)
  • 非事务方法调用事务方法(this)
  • 事务方法的异常被捕获了(spring无法感知事务异常)
  • 事务方法抛出异常类型不对(异常不会被Spring捕获)
  • 事务传播行为使用错误(无法全部回滚)
  • Bean没有被Spring管理(spring无法代理)

6. 总结对比

6.1 代理模式分类

类型方式特点
静态代理-接口实现同一接口需手写代理类,被代理类需实现接口
静态代理-子类继承被代理类需手写子类,不要求接口
JDK 动态代理动态生成接口实现类无需手写,要求被代理类实现接口
CGLIB 动态代理动态生成子类无需手写,不要求接口,不能代理 final 类/方法

6.2 核心要点

  • 代理模式的本质:不修改原始代码,在方法前后添加增强逻辑
  • 静态代理需要手动编写代理类,扩展性差
  • 动态代理自动生成代理类,JDK 基于接口,CGLIB 基于继承
  • AOP是 Spring 利用动态代理实现的,@Transactional就是典型应用
  • 事务失效的根本原因:要么绕过了代理对象(this 调用),要么无法生成代理(final 修饰)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 12:44:41

Vue3——第 10 篇:插槽(Slot)——打造高复用组件的利器

本篇定位:插槽是 Vue 组件化体系中最强大的扩展机制之一,它让组件变得高度可定制化。本篇将系统讲解插槽的三种类型——默认插槽、具名插槽和作用域插槽,以及它们的高级用法。读完本篇,你将能够设计出灵活、可复用、语义清晰的组件…

作者头像 李华