专栏首页业余草看完这篇文章再也不怕面试被问@transactional不生效的原因了

看完这篇文章再也不怕面试被问@transactional不生效的原因了

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

推荐:https://www.xttblog.com/?p=5051

简介

代理设计模式,用于无侵入性地增强方法功能。代理模式是指,目标类的方法执行,需要委托给代理类执行,代理类可以在执行目标方法前/后,处理一些其它事情,这样就可以起到增强目标方法的作用,例如,需要在目标方法前获取运行时间。

代理又分为静态代理和动态代理,静态代理的代理类二进制文件是在编译时生成的,然而静态代理的代理类二进制文件是在运行时生成并加载到虚拟机环境的。

下图是典型的静态代理模式类图。

举个栗子。假如有个UserService接口,它拥有一个保存用户的方法。UserServiceImpl类实现了保存用户的方法,此时有个需求是统计保存用户这个方法的运行时间,通过静态代理的方式实现该功能如下:

public interface UserService {
    void saveUser();
}
public class UserServiceImpl implements UserService {
    public void saveUser() {
        System.out.println("用户数据已经保存");
    }
}

方法增强处理器。

//方法增强处理器
public interface MethodEnhanceHandler {
    /**
     * 抛出异常时调用
     */
    void doThrowing(Exception e);
    /**
     * 目标方法执行前调用
     */
    void doBefore();
    /**
     * 目标方法执行后调用
     */
    void doAfter();
}

代理类实现代码如下:

public class UserServiceProxy implements UserService {
    private final MethodEnhanceHandler methodEnhanceHandler;
    private final UserService target;
    public UserServiceProxy(UserService target, MethodEnhanceHandler methodEnhanceHandler) {
        this.target = target;
        this.methodEnhanceHandler = methodEnhanceHandler;
    }
   @Override
    public void saveUser() {
        try {
            if (methodEnhanceHandler != null) {
                methodEnhanceHandler.doBefore();
            }
            target.saveUser();
        } catch (Exception e) {
            if (methodEnhanceHandler != null) {
                methodEnhanceHandler.doThrowing(e);
            }
        } finally {
            if (methodEnhanceHandler != null) {
                methodEnhanceHandler.doAfter();
            }
        }
    }
}

测试代码如下:

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userService, new MethodEnhanceHandler() {
            public void doThrowing(Exception e) {
                System.out.println("方法出错了");
            }

            public void doBefore() {
                System.out.println("记录方法运行前的时间戳");
            }

            public void doAfter() {
                System.out.println("记录方法运行后的时间戳");
            }
        });
        proxy.saveUser();
    }
}

上面是流行的AOP切面编程的一种实现方式。如果要按照上述的方式使用代理模式,那么真实角色必须是实现已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,但如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

在JDK中,提供了一种实现动态代理的方案,但速度慢,而且,它是基于接口的,在下个章节中,我们通过一个例子来感性认识。在开源界提供了几种动态代理库,其中著名并广泛使用的有两种,一种是cglib,在spring中使用它作为动态代理的实现方案,它的底层基于ASM实现;另一种是javassist。

为了让大家可以感性认识JDK/CGLIB/JAVASSIST三种动态代理方案的使用,下面提个小需求,就是实现一个简单的AOP。

AOP 的需求

有个Person类,它拥有医生的技能(会诊),和办公室工作人员的技能(打印文件),然后利用前面提供的方法增强器,在方法执行前、执行后、抛异常时,执行相应的方法处理。

为了阅读方便,我再将上面的方法增强处理器实现代码摘录如下:

//方法增强处理器
public interface MethodEnhanceHandler {
    /**
     * 抛出异常时调用
     */
    void doThrowing(Exception e);
    /**
     * 目标方法执行前调用
     */
    void doBefore();
    /**
     * 目标方法执行后调用
     */
    void doAfter();
}

为了达到预期的效果,我们先新建两个接口:

public interface DoctorSkill {
    void consult();
}
public interface OfficerSkill {
    void print(String fileName);
}

接口实现类如下:

public class Person implements DoctorSkill, OfficerSkill {
    private String name;
 @Override
    public void consult() {
        System.out.println("会诊病人");
    }
 @Override
    public void print(String fileName) {
        System.out.println("打印文件:" + fileName);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

基于JDK的动态代理实现

上面的需求,我们先用 JDK 来简单实现一下。

public class JDKDynamicProxy {
    public static Object createProxy(Object target, MethodEnhanceHandler methodEnhanceHandler) {
        return Proxy.newProxyInstance(JDKDynamicProxy.class.getClassLoader(),
                target.getClass().getInterfaces(),
                new InternalInvocationHandler(target, methodEnhanceHandler));
    }
    private static class InternalInvocationHandler implements InvocationHandler {
        private MethodEnhanceHandler methodEnhanceHandler;
        private Object target;
        public InternalInvocationHandler(Object target, MethodEnhanceHandler methodEnhanceHandler) {
            this.target = target;
            this.methodEnhanceHandler = methodEnhanceHandler;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object retVal = null;
            try {
                if (methodEnhanceHandler != null) {
                    methodEnhanceHandler.doBefore();
                }
                retVal = method.invoke(target, args);
            } catch (Exception e) {
                if (methodEnhanceHandler != null) {
                    methodEnhanceHandler.doThrowing(e);
                }
            } finally {
                if (methodEnhanceHandler != null) {
                    methodEnhanceHandler.doAfter();
                }
            }
            return retVal;
        }
    }
}

测试代码如下所示:

public class Main {
    public static void main(String[] args) {
        Person person = new Person();

        // DoctorSkill proxy = (DoctorSkill) JDKDynamicProxy.createProxy(person, new MyMethodEnhanceHandler());
        //  proxy.consult(); //会诊

        OfficerSkill proxy = (OfficerSkill) JDKDynamicProxy.createProxy(person, new MyMethodEnhanceHandler());
        proxy.print("the cat"); //打印文件
    }

    private static class MyMethodEnhanceHandler implements MethodEnhanceHandler {
        public void doThrowing(Exception e) {
            System.out.println("AOP invoked throw a exception");
        }

        public void doBefore() {
            System.out.println("AOP before method invoked");
        }

        public void doAfter() {
            System.out.println("AOP after method invoked");
        }
    }

}

从上面的代码中,可以看出,基于JDk的动态代理,是无法同时调用医生的技能和办公室的技能的,因为代理对象是基于接口实现的。

基于CGLIB的动态代理实现

代理类实现代码如下:

public class CglibDynamicProxy implements MethodInterceptor {
    private MethodEnhanceHandler methodEnhanceHandler;
    private CglibDynamicProxy(MethodEnhanceHandler methodEnhanceHandler) {
        this.methodEnhanceHandler = methodEnhanceHandler;
    }
    public static Object createProxy(final Class target, MethodEnhanceHandler methodEnhanceHandler) {
        //cglib 中加强器,用来创建动态代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target);
        CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy(methodEnhanceHandler);
        enhancer.setCallback(cglibDynamicProxy);
        return enhancer.create();
    }
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object retVal = null;
        try {
            if (methodEnhanceHandler != null) {
                methodEnhanceHandler.doBefore();
            }
            retVal = proxy.invokeSuper(obj, args);
        } catch (Exception e) {
            if (methodEnhanceHandler != null) {
                methodEnhanceHandler.doThrowing(e);
            }
        } finally {
            if (methodEnhanceHandler != null) {
                methodEnhanceHandler.doAfter();
            }
        }
        return retVal;
    }
}

测试实现代码如下:

public class Main {
    public static void main(String[] args) {
        Person proxy = (Person) CglibDynamicProxy.createProxy(Person.class, new MyMethodEnhanceHandler());
        proxy.consult();
        proxy.print("the dog");
    }
    private static class MyMethodEnhanceHandler implements MethodEnhanceHandler {
        public void doThrowing(Exception e) {
            System.out.println("AOP invoked throw a exception");
        }
        public void doBefore() {
            System.out.println("AOP before method invoked");
        }
        public void doAfter() {
            System.out.println("AOP after method invoked");
        }
    }
}

通过上面的两个例子,我们可以得出一些结论。

  • 两者的本质相同,都是生成一个子类来模拟“代理”。
  • cglib 中加强器,调用了 setSuperclass,说明是使用继承。而 java 是单继承的,如果被代理的对象已经继承了其他类,或者是 final 类,就无法使用 cglib 了。
  • JDK 动态代理只能对接口生成代理,而不能针对类,是因为它默认就继承了 Proxy,Java 不能多继承。
  • CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
  • 如果某个类没有实现接口,那么这个类就不能同JDK产生动态代理了!
  • cglib 需要引人额外的包,而 jdk 产生的代理不需要依赖外部的包。

既然如此,那么我们只需要所有机器有相同的时间就行了,如果两个节点之间不需要交互,它们的时间甚至都不需要同步。所以我们只要抓住一个重点:达成一致的是节点之间交互的事件发生顺序,而非时间。

所以有很多数据一致性的不同模型。

基于javassist的动态代理实现

public class JavassistDynamicProxy {
    public static Object createProxy(Class<?> target, MethodEnhanceHandler methodEnhanceHandler)
            throws IllegalAccessException, InstantiationException {
        ProxyFactory factory = new ProxyFactory();
        factory.setSuperclass(target);
        //        //设置过滤器,判断哪些方法调用需要被拦截
        //        factory.setFilter(new MethodFilter() {
        //            public boolean isHandled(Method m) {
        //                return true;
        //            }
        //        });
        Class proxyClass = factory.createClass();
        Object proxy = proxyClass.newInstance();
        //设置拦截器
        ProxyObject proxyObject = (ProxyObject) proxy;
        proxyObject.setHandler(new InternalMethodHandler(methodEnhanceHandler));

        return proxy;
    }
    private static class InternalMethodHandler implements MethodHandler {
        private MethodEnhanceHandler methodEnhanceHandler;
        public InternalMethodHandler(MethodEnhanceHandler methodEnhanceHandler) {
            this.methodEnhanceHandler = methodEnhanceHandler;
        }
        public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
            Object retVal = null;
            try {
                if (methodEnhanceHandler != null) {
                    methodEnhanceHandler.doBefore();
                }
                retVal = proceed.invoke(self, args);
            } catch (Exception e) {
                if (methodEnhanceHandler != null) {
                    methodEnhanceHandler.doThrowing(e);
                }
            } finally {
                if (methodEnhanceHandler != null) {
                    methodEnhanceHandler.doAfter();
                }
            }
            return retVal;
        }
    }
}

测试类的代码如下:

public class Main {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Person proxy = (Person) JavassistDynamicProxy.createProxy(Person.class,new MyMethodEnhanceHandler());
        proxy.consult();
        proxy.print("the pig");
    }
    private static class MyMethodEnhanceHandler implements MethodEnhanceHandler {
        public void doThrowing(Exception e) {
            System.out.println("AOP invoked throw a exception");
        }
        public void doBefore() {
            System.out.println("AOP before method invoked");
        }
        public void doAfter() {
            System.out.println("AOP after method invoked");
        }
    }
}

现在,三种实现代码都有了,通过比较我们会发现。javassist 的实现和 CGLIB 有些类似,但是 Javassist 实现起来更简单。

以前,Javassist 的速度很快,但是随着 jdk 的发展和 CGLIB 新版本的推出,在速度上,差别已经不大了。

我这里的 demo 没有完全体现出 Javassist 的优势。Hibernate 只所以使用 Javassist 是因为他们背后有同一家公司在支持。Spring 使用了 Cglib,但是 Cglib 的底层是 ASM,ASM 的学习难度比 Javassist 高。Spring 还使用了 AspectJ,但是 Spring 只是使用了与 AspectJ 一样的注解,没有使用 AspectJ 的编译器。

结语

学习了动态代理的底层实现原理后,相信你再也遇不到 @Transactional 事务失效的问题了。反过来,在面试中,通过问 @Transactional 问题,就能知道你是不是在背书,是不是理解了动态代理!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 来自 BAT 大牛总结的常用设计模式汇总

    原文链接:https://cnblogs.com/chenshuyong/p/9998164.html

    业余草
  • 超赞,项目封装:统一结果,统一异常,统一日志

    来源:juejin.im/post/5e073980f265da33f8653f2

    业余草
  • mina的编码和解码以及断包的处理,发送自定义协议,仿qq聊天,发送xml或json

    最近一段时间以来,mina很火,和移动开发一样,异常的火爆。前面写了几篇移动开发的文章,都还不错,你们的鼓励就是我最大的动力...

    业余草
  • java内部类的作用(二)----隐藏作用

    局部内部类有一个优势:即对外部世界完全可以隐藏起来,在这个方法类中的其它方法或者代码都不能调用这个内部类。更不用说其它的类了

    wust小吴
  • java设计模式 (1) 工厂模式,抽象工厂模式,单子模式

    工厂模式就是实例化对象,用工厂方法代替new操作的一种模式,会给你系统带来更大的可扩展性和尽量少的修改量。

    曼路
  • Java设计模式(十)----桥接模式

    桥接模式 (Bridge) 一、定义 二、结构 三、具体案例 1.传统方法 2.使用桥接模式 ...

    汤高
  • ASP.NET MVC三个重要的描述对象:ParameterDescriptor

    Model绑定是为作为目标Action的方法准备参数列表的过程,所以针对参数的描述才是Model绑定的核心。在ASP.NET MVC应用编程接口中,服务于Mod...

    蒋金楠
  • ASP.NET.Core中使用AutoMapper

       接下来创建一个类来继承AutoMapper的Profile类与实现刚才创建的标志接口IProfile,并且在构造函数中配置关系映射

    莫问今朝
  • Entity Framework Core 2.0 新特性

    晓晨
  • Java每日一练(2017/7/15)

    最新通知 ●回复"每日一练"获取以前的题目! ●【新】Ajax知识点视频更新了!(回复【学习视频】获取下载链接) ●答案公布时间:为每期发布题目的第二天 ★【新...

    Java学习

扫码关注云+社区

领取腾讯云代金券