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(); } } } } }
测试结果:
啥这样执行的呢? 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的大致流程过程如下:
上面过程中,生成字节码文件的这个步骤非常重要,这个字节码文件作为参数传入本地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()); } } }
反编译的类中定义了四个方法:
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动态代理和cglib动态代理底层实现原理详细解析(cglib动态代理篇)