cglib 和 jdk代理实现Transaction

本文的读者希望能对数据库事务、spring事务、spring AOP相关概念、Java注解、Java反射、Java代理等技术有一定的了解。

作为开发人员,我相信大家都会遇到这样的一个业务场景:一个业务方法90%的逻辑都是在做查询,只有最后一部分才是对数据的更新。如果更新失败则业务回滚。常见的做法就是在该方法上加一个@Transaction的注解(本文只讲解spring的声明式事务的用法),或者在类上加@Transaction注解。如果将该方法拆成一个查询方法一个新增方法,在新增方法上加@Transaction事务还会生效吗?

[java] view plain copy
  1. public class Test {  
  2.   
  3.     @Transaction  
  4.     public void 发优惠券() {  
  5.         校验用户是否是注册用户();  
  6.         校验用户是否已经发过优惠券();  
  7.         新增优惠券();  
  8.     }  
  9. }  
[java] view plain copy
  1. @Transaction  
  2. public class Test {  
  3.   
  4.     public void 发优惠券() {  
  5.         校验用户是否是注册用户();  
  6.         校验用户是否已经发过优惠券();  
  7.         新增优惠券();  
  8.     }  
  9. }  

这两种方式都可以实现该功能,但是如果说要优化这部分代码,将查询的业务从事务中剥离,缩短事务时间。修改代码如下:

[java] view plain copy
  1. public class Test {  
  2.   
  3.     public void 发优惠券() {  
  4.         校验用户是否是注册用户();  
  5.         校验用户是否已经发过优惠券();  
  6.         新增优惠券();  
  7.     }  
  8.       
  9.     @Transaction  
  10.     <span style="color:#ff0000;">public</span> void 新增优惠券() {  
  11.         新增优惠券();  
  12.     }  
  13. }  
[java] view plain copy
  1. public class Test {  
  2.   
  3.     public void 发优惠券() {  
  4.         校验用户是否是注册用户();  
  5.         校验用户是否已经发过优惠券();  
  6.         新增优惠券();  
  7.     }  
  8.       
  9.     @Transaction  
  10.     <span style="color:#ff0000;">private</span> void 新增优惠券() {  
  11.         新增优惠券();  
  12.     }  
  13. }  

如果有这个经历的应该知道,这两种方式@Transaction根本没有开启事务,也就是根本没有起作用。理想是美好的,现实是残酷的。But Why?

接下来请大家跟随我一步一步的来了解真正的@Transaction。

首先我们先来模拟下spring的@Transaction实现(此处很重要,请同学们一定要认真看)。众所周知spring的@Transaction是使用AOP等技术实现的,说白了就是Java的动态代理。而Java的动态代理有两种:jdk动态代理、cglib动态代理。

  •  Jdk动态代理模拟@Transaction实现
[java] view plain copy
  1. public interface BookFacade {  
  2.     void addBook();  
  3.     void query();  
  4. }  
[java] view plain copy
  1. public class BookFacadeImpl implements BookFacade {  
  2.    @Transaction  
  3.     public void addBook() {  
  4.        query();  
  5.        System.out.println("增加图书方法。。。");  
  6.     }  
  7.       
  8.     public void query() {  
  9.        System.out.println("查询是否可以增加图书");  
  10.     }  
  11.    
  12. }  
[java] view plain copy
  1. public class BookFacadeProxy implements InvocationHandler {  
  2.    
  3.     private Object target;  
  4.    
  5.     /** 
  6.     * 绑定委托对象并返回一个代理类 
  7.     *  
  8.     * @param target 
  9.     * @return 
  10.     */  
  11.     public Object bind(Object target) {  
  12.        this.target = target;  
  13.        // 取得代理对象  
  14.        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); // 要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)  
  15.     }  
  16.    
  17.     /** 
  18.     * 调用方法 
  19.     */  
  20.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  21.        System.out.println("invoke");  
  22.        Object result = null;  
  23.        Annotation annotation = target.getClass()  
  24.                                      .getDeclaredMethod(method.getName(), method.getParameterTypes())  
  25.                                      .getAnnotation(Transaction.class);  
  26.        if (annotation == null) {  
  27.            result = method.invoke(target, args);  
  28.            return result;  
  29.        } else {  
  30.            System.out.println("事物开始");  
  31.            result = method.invoke(target, args);  
  32.            System.out.println("事物结束");  
  33.            return result;  
  34.        }  
  35.     }  
  36.    
  37. }  
[java] view plain copy
  1. /** 
  2.  * 类Transaction.java的实现描述:模拟spring的transaction 
  3.  */  
  4. @Target({ElementType.TYPE,ElementType.METHOD})  
  5. @Retention(RetentionPolicy.RUNTIME)  
  6. @Inherited  
  7. @Documented  
  8. public @interface Transaction {  
  9.    
  10. }  
[java] view plain copy
  1. public class TestProxy {  
  2.    
  3.     public static void main(String[] args) {  
  4.        BookFacadeProxy proxy = new BookFacadeProxy();  
  5.        BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl());  
  6.        bookProxy.addBook()  
  7.     }  
  8. }  

运行结果如下:
cglib 和 jdk代理实现Transaction

发现没有?invoke只打印了一次,也就是说jdk动态代理只有在外部调用其方法时才会代理调用,自己调用自己的方法是不会走代理调用的。如果将@Transaction加在query()上是不会起作用的(请自行动手尝试)。那用cglib又如何呢?

  •  Cglib代理模拟@Transaction实现

[java] view plain copy
  1. public class BookFacadeImpl {  
  2.    
  3.     @Transaction  
  4.     public void addBook() {  
  5.        query();  
  6.        System.out.println("增加图书方法。。。");  
  7.     }  
  8.       
  9.     public void query() {  
  10.        System.out.println("查询是否可以增加图书");  
  11.     }  
  12. }  
[java] view plain copy
  1. <pre name="code" class="java">public class BookFacadeCglib implements MethodInterceptor {  
  2.    
  3.     private Object target;  
  4.    
  5.     /** 
  6.     * 创建代理对象 
  7.     *  
  8.     * @param target 
  9.     * @return 
  10.     */  
  11.     public Object getInstance(Object target) {  
  12.        this.target = target;  
  13.        Enhancer enhancer = new Enhancer();  
  14.        enhancer.setSuperclass(this.target.getClass());  
  15.        // 回调方法  
  16.        enhancer.setCallback(this);  
  17.        // 创建代理对象  
  18.        return enhancer.create();  
  19.     }  
  20.    
  21.     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
  22.        Annotation annotation = obj.getClass()  
  23.                                   .getSuperclass()  
  24.                                   .getDeclaredMethod(method.getName(), method.getParameterTypes())  
  25.                                   .getAnnotation(Transaction.class);  
  26.        System.out.println("invoke");  
  27.        if (annotation == null) {  
  28.            proxy.invoke(target, args);  
  29.        } else {  
  30.            System.out.println("事务开始");  
  31.            proxy.invokeSuper(obj, args);  
  32.            System.out.println("事务结束");  
  33.        }  
  34.        return null;  
  35.     }  
  36. }  

[java] view plain copy
  1. public class TestCglib {  
  2.    
  3.     public static void main(String[] args) {  
  4.        BookFacadeCglib cglib = new BookFacadeCglib();  
  5.        BookFacadeImpl bookCglib =(BookFacadeImpl) cglib.getInstance(new BookFacadeImpl());  
  6.        bookCglib.addBook();  
  7.     }  
  8. }  

运行结果如下:

cglib 和 jdk代理实现Transaction

惊喜的发现跟jdk差别好大,自己调用自己的方法也是代理调用。那么也就是说spring的@Transaction如果是用cglib代理实现的话前面的优化代码是可行的,看下面的结果:

cglib 和 jdk代理实现Transaction

不用多说,这种形式肯定是cglib代理实现的。按照刚才的结论,这个@Transaction是会生效的,即该测试用例不会执行成功,因为我标注的是只读事务,只读事务中进行新增操作是会报错的,运行结果:

cglib 和 jdk代理实现Transaction

与预期的不一样。

下面来看spring是如何实现的

cglib 和 jdk代理实现Transaction

这两个类就类似我们模拟时的BookFacadeCglib类,我们首先来看CglibAopProxy类,其代理调用的核心实现如下:

cglib 和 jdk代理实现Transaction

1处我们可以理解成去扫描调用的方法上是否有@Transaction注解,即impl.add()的add()方法是否有注解,我们的例子中是没有注解,于是会走到分支2处,此处的注释很清楚,直接用目标对象调用add()方法,故add()中调用test()时也是目标对象在调用,而@Transaction的advise都是在代理对象上,所以自己调用自己的方法@Transaction是不生效的。按照这种说法是不是说在add()方法上加了@Transaction后test()方法上的注解就会生效,即测试用例报错,我们再来看看结果:

cglib 和 jdk代理实现Transaction

再来看jdk的动态代理:

cglib 和 jdk代理实现Transaction

也是一样的实现。

      至于如何能够让自己调用自己时@Transaction生效请参考文章:http://jinnianshilongnian.iteye.com/blog/1487235里面非常详细的描述了如何实现。如果对整个spring声明式事务感兴趣的也可以参考下面一篇文章去学习,该文章是从tx:annotation-driven如何生效讲起的,直到spring事务的各种传播机制是如何失效的:https://doanduyhai.wordpress.com/2011/11/20/spring-transactional-explained/

       到此,我的分析结束,如果还有不明白的欢迎留言交流。