我们知道,JDK 动态代理可以对接口进行代理,把接口的所有方法代理到 InvocationHandler 的 invoke 方法上。
示例如下:
package com.youthlin.example.proxy.service; public interface IUserService { String getName(); }
package com.youthlin.example.proxy; import com.youthlin.example.proxy.service.IUserService; import sun.misc.ProxyGenerator; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 创建: youthlin.chen * 时间: 2017-11-28 11:49 */ public class Main { public static void main(String[] args) throws IOException { IUserService userService = (IUserService) Proxy.newProxyInstance( IUserService.class.getClassLoader(), new Class[]{IUserService.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return "YouthLin"; } }); System.out.println("不同包接口 " + userService.getName()); //writeProxyClass(userService.getClass()); } }
这样我们就可以对一个 没有实现 的接口进行调用了,实际的调用代理给了 IncovationHandler
实例的 invoke
方法。那么这个是怎么实现的呢?
我们可以使用 sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[])
这个方法获取到生成的代理类的字节码数组,把字节码输出为一个 .class 文件,在 IDEA 中就可以看到源码。
private static void writeProxyClass(Class<?> proxyClass) throws IOException { String proxyClassName = proxyClass.getName(); //System.writeProxyClass.println(proxyClassName); proxyClassName = proxyClassName.substring(proxyClassName.lastIndexOf(".") + 1); //System.writeProxyClass.println(proxyClassName); byte[] proxyClassBytes = ProxyGenerator.generateProxyClass(proxyClassName, new Class[]{IHelloService.class}); String path = Main.class.getResource("/").getPath() + "/generate/"; File file = new File(path, proxyClass.getName().replaceAll("\\.", "/") + ".class"); if (!file.getParentFile().exists()) { //noinspection ResultOfMethodCallIgnored file.getParentFile().mkdirs(); } System.out.println(proxyClass.getName() + " " + file.getAbsolutePath()); FileOutputStream out = new FileOutputStream(file); out.write(proxyClassBytes); out.flush(); out.close(); }
然后在 main 方法里调用 writeProxyClass(userService.getClass()); 就可以了。用 IDEA 打开输出文件,可以看到:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import com.youthlin.example.proxy.Main.IHelloService; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy1 extends Proxy implements IHelloService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy1(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 String sayHello(String var1) throws { try { return (String)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", Class.forName("java.lang.Object")); m3 = Class.forName("com.youthlin.example.proxy.Main$IHelloService").getMethod("sayHello", Class.forName("java.lang.String")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
也就是说,生成动态代理对象时,实际上是 JDK 动态编译了一个类,继承了 Proxy
类,实现了传入的接口类。然后把接口的方法(及 Object 的三个实例方法 toString, hashCode, equals)都代理给了父类 Proxy 的 InvocationHandler
实例。所以我们可以在自定义的 InvocationHandler 的 invoke 方法中进行处理。
Proxy
java.lang.reflect.Proxy#newProxyInstance
这个方法用于生成代理对象,里面比较重要的就是 Class<?> cl = getProxyClass0(loader, intfs);
这一句,拿到代理类的 Class 实例,之后 return cons.newInstance(new Object[]{h});
即可生成代理对象。
java.lang.reflect.Proxy#getProxyClass0
这个方法实现很简单: return proxyClassCache.get(loader, interfaces);
从一个 proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
缓存中获取类实例。其实真正的获取在这个地方:
java.lang.reflect.Proxy.ProxyClassFactory#apply
首先一些权限检查之类的,略过,拿到代理对象的类名,最终也落在 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);
这个调用上。最后通过本地方法将 byte[] 转化为 Class 对象就完成了。
ProxyGenerator
sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)
sun.misc.ProxyGenerator#generateClassFile
这里就是动态生成字节码的地方了。
其实是根据 JVM 规范,把类信息写入了字节数组
try { var14.writeInt(-889275714); var14.writeShort(0); var14.writeShort(49); this.cp.write(var14); var14.writeShort(this.accessFlags); var14.writeShort(this.cp.getClass(dotToSlash(this.className))); var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy")); var14.writeShort(this.interfaces.length); Class[] var17 = this.interfaces; int var18 = var17.length; for(int var19 = 0; var19 < var18; ++var19) { Class var22 = var17[var19]; var14.writeShort(this.cp.getClass(dotToSlash(var22.getName()))); } var14.writeShort(this.fields.size()); var15 = this.fields.iterator(); while(var15.hasNext()) { ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next(); var20.write(var14); } var14.writeShort(this.methods.size()); var15 = this.methods.iterator(); while(var15.hasNext()) { ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next(); var21.write(var14); } var14.writeShort(0); return var13.toByteArray(); } catch (IOException var9) { throw new InternalError("unexpected I/O Exception", var9); }
对应下面的 Class 规范:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
本文代码可以在这里找到:
https://github.com/YouthLin/examples/tree/master/example-jdk-proxy
声明
- 本作品采用署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。除非特别注明, 霖博客文章均为原创。
- 转载请保留本文(《JDK 动态代理的实现》)链接地址: https://youthlin.com/?p=1647
- 订阅本站:https://youthlin.com/feed/
“JDK 动态代理的实现”上的1条回复
之前看过,忘的一干二净了。。