JDK动态代理原理

Posted 有爱jj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 Files\Java\jdk1.8.0_101\bin\java"...
Before invocation
Hello cjj!
After invocation
请到F:\cjj\cjjwork\my_java_test\target\classes\cjj\proxy\jdk目录下查找生成的ProxySubject.class代理文件

 

啥这样执行的呢? invoke是什么时候调用的呀?

刚开始接触动态代理的时候就会纳闷了,查阅资料以及看源码大致知道了jdk动态代理是如何工作的,下面进行简单阐述。

首先要明白“动态代理对象”这个概念,我理解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:对于上面小结中的第1点,编译好的.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不会创建,问题出在哪儿暂时不清楚)

 

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

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

本文在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

反编译的类中定义了四个方法:

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 -- 代理类

HelloWorldImpl -- 真实对象

HelloWorld -- 代理接口

 

以上是关于JDK动态代理原理的主要内容,如果未能解决你的问题,请参考以下文章

转JDK动态代理原理

java动态代理:JDK动态代理的内部原理

jdk动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)

JDK动态代理实现原理

设计模式 - 动态代理原理及模仿JDK Proxy 写一个属于自己的动态代理

代理模式(静态代理,JDK动态代理,JDK动态代理原理分析)