博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JDK动态代理原理
阅读量:6848 次
发布时间:2019-06-26

本文共 9632 字,大约阅读时间需要 32 分钟。

 

在介绍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动态代理是啥回事了。使用动态代理的过程大致为:首先获取”动态代理对象“,然后通过这个对象调用”真实对象“的方法。是不是有点不知所云,那继续看会。我们在回头看看MainTest中,取jdk动态代理对象的两种方式

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

其实这两种方式的底层原理是一样的,这里就不分析源码了,如果想跟着源码走,可以参考

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

getProxyClass0的大致流程过程如下:

--->
getProxyClass0(loader, intfs)
--->
proxyClassCache.get(loader, interfaces), 
首先冲缓存中取,没有再创建
---> supplier.get()
supp
lier是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 -- 代理接口

 

转载于:https://www.cnblogs.com/chenjunjie12321/p/9014926.html

你可能感兴趣的文章
tomcat redis session共享(包含redis安全设置)
查看>>
iptables中DNAT、SNAT和MASQUERADE的作用
查看>>
kvm命令学习记录
查看>>
小菜鸡进阶之路-First week
查看>>
ORACLE 10g SYSAUX表空间快速增长之WRH$_ACTIVE_SESSION_HISTORY篇
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
子数组的和的最大值(包括升级版的首尾相连数组)
查看>>
LeetCode - Nth Highest Salary
查看>>
9.ORM数据访问
查看>>
href=“javascript:”vs href=“javascript:void(0)”
查看>>
win10文件夹无法打开,双击闪屏
查看>>
【学习笔记14】全局类型转换器
查看>>
Spring Boot学习记录手册<1>
查看>>
在Word2007和Word2010中插入视频文件,并自动在word中播放
查看>>
javascript设置http请求的头信息
查看>>
C++调用java开启远程调试
查看>>
struts2与ajax交互
查看>>
Linux Shell脚本中点号和source命令
查看>>
Unix常用基本数据类型
查看>>