美文网首页深度解析Spring5源码
27--静态代理模式和JDK、CGLIB动态代理

27--静态代理模式和JDK、CGLIB动态代理

作者: 闲来也无事 | 来源:发表于2018-11-05 14:28 被阅读41次

    前面的章节,已经分析了IoC容器的源码,接下来的章节来分析Spring的另一个核心功能AOP。为了更好的分析源码,需要先温习一下动态代理的知识,如果对java的动态代理无所了解的话,那么对AOP源码的分析就无从谈起。代理模式可分为静态代理和动态代理两种。而动态代理又有JDK、CGLIB动态代理。下面我们逐步分析这几种代理。

    1.静态代理

    • 被代理接口和实现类
    package com.lyc.cn.v2.day04.proxy.proxy;
    
    /**
     * 账户接口
     */
    public interface Count {
        // 查询账户
        void queryCount();
    
        // 修改账户
        void updateCount();
    }
    
    package com.lyc.cn.v2.day04.proxy.proxy;
    
    /**
     * @author: LiYanChao
     * @create: 2018-10-21 12:07
     */
    public class CountImpl implements Count {
        @Override
        public void queryCount() {
            System.out.println("==查询账户");
        }
    
        @Override
        public void updateCount() {
            System.out.println("==更新账户");
        }
    }
    

    接口只模拟了两个接口,账户查询和账户更新,并在实现类里进行了打印。

    • 代理类
    package com.lyc.cn.v2.day04.proxy.proxy;
    
    /**
     * 代理类
     * @author: LiYanChao
     * @create: 2018-10-21 12:08
     */
    public class CountProxy implements Count {
    
        private CountImpl countImpl;
    
        /**
         * 覆盖默认构造器
         */
        public CountProxy(CountImpl countImpl) {
            this.countImpl = countImpl;
        }
    
        @Override
        public void queryCount() {
            System.out.println("==查询账户开始");
            // 调用真正的查询账户方法
            countImpl.queryCount();
            System.out.println("==查询账户结束");
        }
    
        @Override
        public void updateCount() {
            System.out.println("==更新账户开始");
            // 调用真正的修改账户操作
            countImpl.updateCount();
            System.out.println("==更新账户结束");
        }
    }
    
    • 测试及结果
    @Test
    public void test1() {
        // 静态代理
        CountImpl countImpl = new CountImpl();
        CountProxy countProxy = new CountProxy(countImpl);
        countProxy.updateCount();
        System.out.println("\n*******\n");
        countProxy.queryCount();
    }
    
    ==更新账户开始
    ==更新账户
    ==更新账户结束
    
    *******
    
    ==查询账户开始
    ==查询账户
    ==查询账户结束
    

    该中模式比较简单,不再做过多的分析。

    2.JDK动态代理

    JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。JDK的动态代理需要实现InvocationHandler接口,并重写invoke方法。并且被代理的类必须有接口,来看具体的例子:

    • 被代理接口和类
    package com.lyc.cn.v2.day04.proxy.jdk;
    
    public interface JDKAnimal {
        void sayHello();
    }
    
    package com.lyc.cn.v2.day04.proxy.jdk;
    
    public class JDKDog implements JDKAnimal {
        @Override
        public void sayHello() {
            System.out.println("我是一只猫");
        }
    }
    
    • 自定义InvocationHandler
    package com.lyc.cn.v2.day04.proxy.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class MyInvocationHandler implements InvocationHandler {
    
        // 目标对象
        private Object target;
    
        /**
         * 构造方法
         * @param target 目标对象
         */
        public MyInvocationHandler(Object target) {
            super();
            this.target = target;
        }
    
        /**
         * @param proxy  JDK动态生成的最终代理对象
         * @param method 调用真实对象的某个方法的Method对象
         * @param args   调用真实对象某个方法时接受的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("==代理方法开始执行");
            Object invoke = method.invoke(target, args);
            System.out.println("==代理方法结束执行");
            return invoke;
        }
    
        /**
         * 获取目标对象的代理对象
         * @return 代理对象
         */
        public Object getProxy() {
            /**
             * 参数:
             * 1、获取当前线程的类加载
             * 2、获取接口
             * 3、当前对象
             */
            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    target.getClass().getInterfaces(),
                    this);
        }
    
    }
    
    • 测试及结果
    @Test
    public void test2() {
        // JDK动态代理
        MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
        JDKAnimal proxy = (JDKAnimal) handler.getProxy();
        proxy.sayHello();
    }
    
    ==代理方法开始执行
    我是一只猫
    ==代理方法结束执行
    
    3.JDK动态代理原理分析

    在MyInvocationHandler类中,通过实现InvocationHandler并重写invoke方法,实现了对JDKDog类的动态代理。但是这里大家一定会有一个疑问,在测试类中通过JDKAnimal proxy = (JDKAnimal) handler.getProxy();获取了代理类的实例,但是当调用proxy.sayHello();方法时,却会调用MyInvocationHandler的invoke方法,要解开这个谜题,就需要了解一下代理类是如何生成、生成的代理类是什么样子的、是如何被实例化的。

    • Proxy.newProxyInstance方法简析
      打开MyInvocationHandler类的getProxy方法,查看Proxy.newProxyInstance方法源码:
    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
    
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
    
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
    
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
    
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 创建生成代理类的实例
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }
    

    如果大家对这段代码不感兴趣或者觉得很枯燥的话,没关系,大家只要知道在这个方法里生成了代理类的实例就行了,但是生成的代理类是什么样子的呢?可以通过ProxyGenerator来帮助我们。

    • 生成代理类的.class文件
    package com.lyc.cn.v2.day04.proxy.jdk;
    
    import org.junit.Test;
    import sun.misc.ProxyGenerator;
    
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    
    /**
     * @author: LiYanChao
     * @create: 2018-11-02 14:11
     */
    public class GenJdkProxyClass {
    
        /**
         * 生成代理类的名称
         */
        private static String DEFAULT_CLASS_NAME = "$Proxy";
    
        /**
         * 默认生成的文件全路径
         */
        private static String DEFAULT_FILE_PATH = "/Users/liyanchao/Desktop/" + DEFAULT_CLASS_NAME + ".class";
    
    
        /**
         * 使用ProxyGenerator生成代理类.class文件
         * @param path 文件路径
         */
        public static void genProxyClass(String path) {
    
            byte[] classFile = ProxyGenerator.generateProxyClass(DEFAULT_CLASS_NAME, new Class[]{JDKAnimal.class});
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(path);
                fos.write(classFile);
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        @Test
        public void TestGenProxyClass() {
            GenJdkProxyClass.genProxyClass(DEFAULT_FILE_PATH);
        }
    }
    

    可以根据自己的需要修改生成的类名和路径。运行测试方法生成.class文并进行反编译,如果没有反编译软件的话,可以下载JD-GUI 可以实现对.class文件的反编译,该软件支持windows、linux、macOS。用JD-GUI打开生成的$Proxy.class文件,并将反编译的类导入IDEA(直接在IDEA里新建同名文件将代码复制粘贴进去即可)。反编译后的源码如下:

    package com.lyc.cn.v2.day04.proxy.jdk;
    
    /**
     * 反编译代理.class文件
     * @author: LiYanChao
     * @create: 2018-11-02 15:01
     */
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy extends Proxy implements JDKAnimal {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public $Proxy(InvocationHandler paramInvocationHandler) {
            super(paramInvocationHandler);
        }
    
        public final boolean equals(Object paramObject) {
            try {
                return ((Boolean) this.h.invoke(this, m1, new Object[]{paramObject})).booleanValue();
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    
        /**
         * 代理类中实现了sayHello接口
         */
        public final void sayHello() {
            try {
                this.h.invoke(this, m3, null);
                return;
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    
        public final String toString() {
            try {
                return (String) this.h.invoke(this, m2, null);
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    
        public final int hashCode() {
            try {
                return ((Integer) this.h.invoke(this, m0, null)).intValue();
            } catch (Error | RuntimeException localError) {
                throw localError;
            } catch (Throwable localThrowable) {
                throw new UndeclaredThrowableException(localThrowable);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m3 = Class.forName("com.lyc.cn.v2.day04.proxy.jdk.JDKAnimal").getMethod("sayHello", new Class[0]);
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                // return 会报错,注释掉即可。
                //return;
            } catch (NoSuchMethodException localNoSuchMethodException) {
                throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
            } catch (ClassNotFoundException localClassNotFoundException) {
                throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
            }
        }
    }
    

    代理类$Proxy被声明为final类,继承Proxy类并实现了JDKAnimal接口,重写了equals、toString、hashCode等方法,当然最重要的还是实现了JDKAnimal接口中的sayHello方法,并通过静态代码块拿到了sayHello方法的信息,看到sayHello方法体,看到这里相信大家对前面提到的疑问已经恍然大悟了。再通过一个测试方法加深大家的印象:

    @Test
    public void test3() {
        // JDK动态代理测试反编译后的生成代理类
        MyInvocationHandler handler = new MyInvocationHandler(new JDKDog());
        $Proxy $proxy = new $Proxy(handler);
        $proxy.sayHello();
    }
    

    new $Proxy(handler)构造函数接受的就是我们自定义的MyInvocationHandler,所以当代码运行到$Proxy的sayHello方法时,this.h.invoke(this, m3, null); this.h就是MyInvocationHandler的实例,所以自然就会调用到invoke方法了,因为JDKAnimal proxy = (JDKAnimal) handler.getProxy();获取到的是代理类的实例,而不是JDKAnimal的实例。

    4.CGLIB动态代理

    CGLIB是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理。因为采用的是继承,所以不能对final修饰的类进行代理。 在使用的时候需要引入cglib和asm的jar包,并实现MethodInterceptor接口。

    本例是在Spring源码下新建的Gradle模块,所以引入jar包的方式如下,如果是maven工程或者其他的工程的话,可自行导入jar包。

    compile group: 'asm', name: 'asm', version: '3.3.1'
    compile group: 'cglib', name: 'cglib', version: '2.2.2'
    
    • 被代理类
    package com.lyc.cn.v2.day04.proxy.cglib;
    
    public class CglibCat {
        public void sayHello() {
            System.out.println("我是一只猫。。。");
        }
    }
    
    • 自定义MethodInterceptor
    package com.lyc.cn.v2.day04.proxy.cglib;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class MyCglibProxy implements MethodInterceptor {
    
        private Enhancer enhancer = new Enhancer();
    
        // 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
        public Object getInstance(Class clazz) {
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            // 返回代理对象
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("==代理方法开始执行");
            Object result = methodProxy.invokeSuper(proxy, args);
            System.out.println("==代理方法结束执行");
            return result;
        }
    
    }
    
    • 测试类及结果
    @Test
    public void test4() {
        // CGLIB动态代理
        CglibCat cat = (CglibCat) new MyCglibProxy().getInstance(CglibCat.class);
        cat.sayHello();
    }
    
    ==代理方法开始执行
    我是一只猫。。。
    ==代理方法结束执行
    

    关于CGLIB的原理,经过多种反编译软件测试,反编译出来的代码好像不是正确的,而且跟网上其他的文章反编译出来的有很大的不同,不知道是不是因为macOS或CGLIB版本的原因,这里就不在粘贴反编译源码了,如果感兴趣的同学可以参考下面这篇文章,cglib原理解析

    5.总结

    总而言之,动态代理技术是AOP的基础也是核心,大家一定要先把JDK、CGLIB动态代理搞明白之后,再去看AOP的源码,才能达到事半功倍的效果。

    相关文章

      网友评论

        本文标题:27--静态代理模式和JDK、CGLIB动态代理

        本文链接:https://www.haomeiwen.com/subject/kpdsxqtx.html