Spring的事务管理难点剖析(6):特殊方法成漏网之鱼
哪些方法不能实施Spring AOP事务
由于Spring事务管理是基于接口代理或动态字节码技术,通过AOP实施事务增强的。虽然Spring还支持AspectJ LTW在类加载期实施增强,但这种方法很少使用,所以我们不予关注。
对于基于接口动态代理的AOP事务增强来说,由于接口的方法都必然是public的,这就要求实现类的实现方法也必须是public的(不能是protected、private等),同时不能使用static的修饰符。所以,可以实施接口动态代理的方法只能是使用“public”或“public final”修饰符的方法,其他方法不可能被动态代理,相应的也就不能实施AOP增强,换句话说,即不能进行Spring事务增强了。
基于CGLib字节码动态代理的方案是通过扩展被增强类,动态创建其子类的方式进行AOP增强植入的。由于使用final、static、private修饰符的方法都不能被子类覆盖,相应的,这些方法将无法实施AOP增强。所以方法签名必须特别注意这些修饰符的使用,以免使方法不小心成为事务管理的漏网之鱼。
事务增强遗漏实例
本节中,我们通过具体的实例说明基于CGLib字节码动态代理无法享受Spring AOP事务增强的特殊方法。
package com.baobaotao.special;import org.springframework.stereotype.Service;@Service("userService")public class UserService { //① private方法因访问权限的限制,无法被子类覆盖 private void method1() { System.out.println("method1"); } //② final方法无法被子类覆盖 public final void method2() { System.out.println("method2"); } //③ static是类级别的方法,无法被子类覆盖 public static void method3() { System.out.println("method3"); } //④ public方法可以被子类覆盖,因此可以被动态字节码增强 public void method4() { System.out.println("method4"); } }Spring通过CGLib动态代理技术对UserService Bean实施AOP事务增强的关键配置,具体如下所示:
… <aop:config proxy-target-advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice>…
在①处,我们通过proxy-target-name="code">package com.baobaotao.special;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.stereotype.Service;@Service("userService")public class UserService { … public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("user/special/applicationContext.xml"); UserService service = (UserService) ctx.getBean("userService"); System.out.println("before method1"); service.method1(); System.out.println("after method1"); System.out.println("before method2"); service.method2(); System.out.println("after method2"); System.out.println("before method3"); service.method3(); System.out.println("after method3"); System.out.println("before method4"); service.method4(); System.out.println("after method4"); }}
在运行UserService之前,将Log4J日志级别设置为DEBUG,运行以上代码查看输出日志,如下所示: