0.代理模式演示代码
代理模式相关演示代码:
github:学习Java代理模式的小项目-链接
1. 为什么需要代理模式
1.1 核心问题
在实际开发中,我们经常需要在方法执行的前后做一些额外的操作,比如:
- 事务管理:方法中往订单表插入数据,如果订单商品表插入失败,需要回滚订单表,整个过程需要事务
- 异步执行:有些方法需要异步提交到线程池执行
- 日志记录:在方法前后记录日志
- 权限校验:方法执行前校验权限
核心诉求:不修改原始类代码,在方法调用的前后自动增强功能。
1.2 代理模式的思想
让代理对象帮我们在方法调用的前后做一些事情:
- 程序员只负责编写业务方法
- 代理对象负责在方法前后添加额外逻辑(开启事务、提交事务、异步提交等)
- 不需要更改原始类的一行代码
2. 静态代理
2.1 方式一:通过接口实现
适用场景:被代理的类实现了接口。
实现方式:
- 定义接口
OrderService,包含createOrder()方法 - 实现类
OrderServiceImpl实现createOrder() - 创建代理类
OrderServiceProxy也实现OrderService接口 - 代理类中持有
OrderServiceImpl对象 - 在代理类的
createOrder()中,调用OrderServiceImpl的方法,并在前后添加增强逻辑
优点:不修改OrderServiceImpl的任何代码。
2.2 方式二:通过子类实现
适用场景:被代理的类没有实现接口。
实现方式:
- 创建被代理类
OrderService的子类 - 通过
super调用父类的createOrder()方法 - 在调用前后添加增强操作
优点:同样不修改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 在运行时:
- 动态使用 CGLIB 或 JDK 动态代理
- 帮你动态创建一个代理对象
- 你只需要加一个
@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 修饰)