一、问题现象:
在程序设计中,有时候我们会遇到一种情况,在一个类的内部进行自我方法调用,然后通过调用这个方法使该
方法进入事务管理,如图:
Spring声明式事务管理的配置如图:
接下来,当我们执行程序后发现控制台打印如下日志:
现在,我们已经知道了本文所描述的问题出现所在,先不解答问题如何处理,我们先了解一下Spring声明式事
务的原理,其实Spring是基于它的AOP动态代理技术来实现声明式事务管理的,那么Spring的AOP动态代理又是怎么
样的呢?接下来我们会做介绍。
二、SpringAop动态代理:
AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的
全部方法,但AOP 代理中的方法与目标对象的方法存在差异:AOP 方法在特定切入点添加了增强处理,并回调了目
标对象的方法。
AOP 代理所包含的方法与目标对象的方法示意图如图所示:
AOP 代理的方法与目标对象的方法
Spring 的 AOP 代理由Spring的IoC 容器负责生成、管理,其依赖关系也由IoC 容器负责管理。因此,AOP
代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。
纵观 AOP 编程,其中需要程序员参与的只有 3 个部分:
1.定义普通业务组件。
2.定义切入点,一个切入点可能横切多个业务组件。
3.定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。
上面 3 个部分的第一个部分是最平常不过的事情,无须额外说明。那么进行 AOP 编程的关键就是定义切入
点和定义增强处理。一旦定义了合适的切入点和增强处理,AOP 框架将会自动生成 AOP 代理,而 AOP 代理的方法大
致有如下公式:
代理对象的方法 = 增强处理 + 被代理对象的方法
在上面这个业务定义中,不难发现 Spring AOP 的实现原理其实很简单:AOP 框架负责动态地生成 AOP 代
理类,这个代理类的方法则由 Advice 和回调目标对象的方法所组成。
对于前面提到的图 2 所示的软件调用结构:当方法 1、方法 2、方法 3 ……都需要去调用某个具有“横
切”性质的方法时,传统的做法是程序员去手动修改方法 1、方法 2、方法 3 ……、通过代码来调用这个具有“横
切”性质的方法,但这种做法的可扩展性不好,因为每次都要改代码。
于是 AOP 框架出现了,AOP 框架则可以“动态的”生成一个新的代理类,而这个代理类所包含的方法 1、
方法2、方法 3 ……也增加了调用这个具有“横切”性质的方法——但这种调用由 AOP 框架自动生成的代理类来负
责,因此具有了极好的扩展性。程序员无需手动修改方法 1、方法 2、方法 3 的代码,程序员只要定义切入点即可
—— AOP框架所生成的 AOP 代理类中包含了新的方法 1、访法 2、方法 3,而 AOP 框架会根据切入点来决定是否要
在方法1、方法 2、方法 3中回调具有“横切”性质的方法。
简而言之:AOP 原理的奥妙就在于动态地生成了代理类。
接下来,来看下我们的业务中,如果要使用AOP,那么过程应该是这样的,如下图所示:
但是,实际上我们的代码是这样执行的,如下图:
这样的结果就是,我们先前提到的最初的问题,调用insertValue()方法后,SpringAop拦截不到方法调用,造
成无法进入事务管理。
三、解决方案1:
此处的解决方案是参照Spring官方对于自身方法调用的解决方法,利用暴露本地线程,代理目标类,来完成自
身调用时,触发AOP拦截,进入事务管理,核心工具类为,AopContext.currentProxy(),从AOP容器中取出代理类,在
内部中重新调用要拦截的方法,从而进入事务管理。
Spring配置如下图:
代码的写法如下图所示:
此时进行数据库操作,控制台打印日志如下:
当整个入库操作执行完成后,会提交事务,如下图:
至此,事务提交完成,此方法可行。
但是,这种方法有一个缺点,具有严重的代码侵入性,导致声明式事务的技术形同虚设,并且如果想取消事
务,注释了Spring配置中的AOP相关配置,会出现非受检异常,导致程序无法运行。
那么,如何避免具有代码侵入性呢?现在,我们使用解决方案2。
四、解决方案2:
此处的解决方案是利用Spring的IOC容器,从IOC容器中重新获得代理类,调用业务类的insertValue()方法,
从而使之进入事务管理,Spring配置如下图:
代码写法如下图:
要在类中注入Spring的应用程序上下文,
此时,执行程序,并且观察控制台日志,如下图:
至此,此解决方案完成。
此方案的优点就是没有严重的代码侵入,并且,当你注释掉SpringAOP配置的时候,运行程序正常,只是不进
入事务管理了而已,没有任何其他问题。
唯一的缺点就是,要求程序设计者必须很清楚,哪里该进入事务,哪里就该从SpringIOC容器中取到此对
象的实例,并调用要进入事务的方法。
最后总结一下就是,程序设计是一门危险的平衡艺术!