• JDK动态代理原理


    在介绍JDK动态代理原理之前,先来一个网上比较经典的关于jdk动态代理的例子:

    package cjj.proxy.jdk;
    
    /**
     * @author chenjunjie
     * @since 2018-05-09
     */
    public interface HelloWorld {
        void sayHello(String name);
    }
    -----------------------------分割线-----------------------------------
    package cjj.proxy.jdk;
    
    /**
     * @author chenjunjie
     * @since 2018-05-09
     */
    public class HelloWorldImpl implements HelloWorld {
    
        @Override
        public void sayHello(String name) {
            System.out.println("Hello " + name);
        }
        
    }
    -------------------------------分割线----------------------------------
    package cjj.proxy.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * @author chenjunjie
     * @since 2018-05-09
     */
    public class MyInvocationHandler implements InvocationHandler {
        private Object target;
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Before invocation");
            Object retVal = method.invoke(target, args);
            System.out.println("After invocation");
            return retVal;
        }
    
    
    }

    测试:

    package cjj.proxy.jdk;
    
    import sun.misc.ProxyGenerator;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.lang.reflect.Proxy;
    
    /**
     * @author chenjunjie
     * @since 2018-05-09
     */
    public class MainTest {
        public static void main(String[] args) throws Exception {
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    
            // 方式一:
            MyInvocationHandler handler = new MyInvocationHandler(new HelloWorldImpl());
            ClassLoader loader = MainTest.class.getClassLoader();
            Class[] interfaces = new Class[]{HelloWorld.class};
            HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(loader, interfaces, handler);
            proxy.sayHello("cjj!");
    
            // 将生成的代理对象的字节码保存到本地
            createProxyClassFile();
    
            /*
            //方式二:
            System.out.println();
            Class proxyClazz = Proxy.getProxyClass(loader,interfaces);
            Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
            HelloWorld proxy_hello = (HelloWorld) constructor.newInstance(handler);
            proxy_hello.sayHello("cjj");
    
            小记:
            方法一、方法二实际上都调用了Proxy中的getProxyClass0(loader,interfaces)方法来获取代理对象.
            */
        }
    
        private static void createProxyClassFile(){
            // 代理生成的class文件名称
            String name = "ProxySubject";
            byte[] data = ProxyGenerator.generateProxyClass(name,new Class[]{HelloWorld.class});
            FileOutputStream out =null;
            try {
                String path = MainTest.class.getResource("").getPath();
                String proxyFilePathName = path + name;
                out = new FileOutputStream(proxyFilePathName+".class");
                System.out.println("请到"+(new File(path)).getAbsolutePath()+"目录下查找生成的"+name+".class代理文件");
                out.write(data);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(null != out) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

     测试结果:

    "D:Program FilesJavajdk1.8.0_101injava"...
    Before invocation
    Hello cjj!
    After invocation
    请到F:cjjcjjworkmy_java_test argetclassescjjproxyjdk目录下查找生成的ProxySubject.class代理文件

    刚开始接触动态代理的时候就会纳闷了,咋这样执行的呢? invoke是什么时候调用的呀? 查阅资料以及看源码大致才主见知道了知道了jdk动态代理是如何工作的,下面进行简单阐述。

    首先要明白“动态代理对象”这个概念,知道这个概念基本也就知道jdk动态代理是啥回事了。使用动态代理的过程大致为:首先获取”动态代理对象“,然后通过这个对象调用”真实对象“的方法。是不是有点不知所云,那继续看会。我们在回头看看MainTest中,取jdk动态代理对象的两种方式

    // 方式1
    Proxy.newProxyInstance(loader, interfaces, handler) 
    
    // 方式2
    Proxy.getProxyClass(loader,interfaces).getConstructor(InvocationHandler.class).newInstance(handler)

    其实这两种方式的底层原理是一样的,这里就不分析源码了,如果想跟着源码走,可以参考《细说JDK动态代理的实现原理》

    我也是参考这篇文章跟着源码来了解jdk动态代理原理的,我这里指出读源码中几个关键点:两种方式首先都会调用getProxyClass0(loader, intfs)。

    getProxyClass0的大致流程过程如下:

    ---> getProxyClass0(loader, intfs)
    ---> proxyClassCache.get(loader, interfaces), 首先冲缓存中取,没有再创建
    ---> supplier.get()supplier是WeakCache内部类Factro实例
    ---> WeakCache.Factory.get()初始化WeakCache会生产valueFactory实例
    ---> valueFactory.apply(key, parameter)在本例中调用时这里的key为ClassLoader对象,parameter为interfaces对象
    ---> Proxy.ProxyClassFactory.apply(loader, intfs)调用Proxy内部类ProxyClassFactory的方法applay()
    ---> ProxyGenerator.generateProxyClass(...)生成字节码文件!!
    ---> defineClass0(...) 调用本地native方法 ,返回代理对象。

    上面过程中,生成字节码文件的这个步骤非常重要,这个字节码文件作为参数传入本地native方法defineClass0(...)便可以得到这个动态代理的类对象。

    对这个类对象使用newInstance()便可以实例化,实例化的对象就可以称为”动态代理对象“了!这个动态代理对象中有调用invoke方法,多以就能对真实对象进行”切面“(AOP)管理啦!

    小结:

    1. 事实上任何接口通过ProxyGenerator.generateProxyClass(...)方法都可以编译为.class文件。(没验证这句话是否正确)

    2. 通过类加载器以及defineClass0等一些步骤将编译的.class文件实例化为动态对象来执行动态逻辑处理。

    刚接触这时可能会有如下几个疑问。

    疑问1:为什么叫动态代理?动态怎么理解?

    其实这里的动态可以理解为代理类是在程序运行的时候产生的,而不是已经写好的,所以叫动态。如果已经写好的代理类那就叫静态代理了,关于静态代理可以参考本文下一篇博客。

    疑问2:编译好的.class文件存放到哪里了呢?

    Proxy.ProxyClassFactory类中apply方法中有:

        // 如果为空,则将proxyPkg赋值为 "com.sun.proxy"
        if (proxyPkg == null) {
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
    
        // 每生产一个代理对象num加1
        long num = nextUniqueNumber.getAndIncrement();
        // 常量proxyClassNamePrefix="$Proxy"
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

    如果你看源码时不忘记调试例子的话,是不是可以见像到com.sun.proxy$Proxy0.class、com.sun.proxy$Proxy2.class这样的内容,这些名称就是编译的代理对象类的字节码地址了,在JDK1.8中,调用Proxy.newProxyInstance(...)后IDEA会自动生成一个com.sun.proxy包,用于存放这些代理对象的字节码。(注:有时候IDEA不会创建,问题出在哪儿暂时不清楚)

    疑问3:什么时候调用invoke()方法的?如何调用invoke?

    为了弄清楚疑问3,很有必须要反编译这些代理类字节码,然后分析反编译的类。

    本文在MainTest中使用了createProxyClassFile方法,通过 ProxyGenerator.generateProxyClass(...)将HelloWorld接口进行动态代理处理。HelloWorld接口对应的代理类字节码反编译文件如下(先大致扫描一下,下文会分析):

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import cjj.proxy.jdk.HelloWorld;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class ProxySubject extends Proxy implements HelloWorld {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public ProxySubject(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void sayHello(String var1) throws  {
            try {
                super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m3 = Class.forName("cjj.proxy.jdk.HelloWorld").getMethod("sayHello", new Class[]{Class.forName("java.lang.String")});
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    View Code

    反编译的类ProxySubject继承了Proxy类以及实现了代理接口HelloWorld

    public final class ProxySubject extends Proxy implements HelloWorld

    其中定义了四个方法:

    equals()

    toString()

    hashCode()

    sayHello()

    前三个方法为Object类自带,这里又覆写了一遍,这里主要结合实例来看看sayHello()方法。文章前面测试类中:

       HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(loader, interfaces, handler);
       proxy.sayHello("cjj!")

    相当于:

       HelloWorld proxy_hello = (HelloWorld) ProxySubject(handler);
       proxy.sayHello("cjj!")

     看看 ProxySubject类中sayHello方法:

        public final void sayHello(String var1) throws  {
            try {
                // 利用反射,执行代理方法
                // 这里的super.h指Proxy.InvocationHandler, 即MyInvocationHandler
                super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    

    说明一下,上面的super.h指Proxy.InvocationHandler,在MainTest中使用方式1生成代理对象时,它在调用 Proxy.newProxyInstance(...)时,你查找该方法源码会将发现这一行:

        return cons.newInstance(new Object[]{h});
    

    这是实际上是在调用了ProxySubject的构造方法:

        public ProxySubject(InvocationHandler var1) throws  {
            super(var1);
        }
    

    也就是去调用Proxy的构造函数,查看Proxy看源码可知,在其构造函数中会将MainTest中的代理类 MyInvocationHandler传递给ProxySubject。(使用方式2同理)

    几个名称:

    ProxySubject -- 生成的动态代理类(JVM中生成)

    MyInvocationHanlder -- 代理类,实现InvocationHandler接口

    HelloWorldImpl -- 真实对象

    HelloWorld -- 代理接口

  • 相关阅读:
    使用eclipse从github导入maven项目
    J2SE 8的Lambda --- Comparator
    J2SE 8的Lambda --- functions
    J2SE 8的Lambda --- 语法
    J2SE 8的流库 --- 收集处理结果
    J2SE 8的流库 --- 转换流, 得到的还是流
    J2SE 8的流库 --- 基本类型流的使用
    J2SE 8的流库 --- 生成流
    Hadoop 3.0 安装
    程序员到底要不要读研,过来人给你几点建议!
  • 原文地址:https://www.cnblogs.com/chenjunjie12321/p/9014926.html
Copyright © 2020-2023  润新知