目录
代理简介?
我们知道nginx可以实现正向,反向代理。比如我们想请求服务中一个tomcat,一般就是直接访问机器的ip,如果是代理的话,就是先访问中间代理层(nginx),然后nignx跳转到我们的tomcat机器。代理模式也是如此,也有一个Proxy层,通过Proxy层来真正访问我们的类接口。
为什么要有代理?
我们先看nginx实现的代理,他可以事先为我们做很多ip黑名单过滤,负载均衡,权限,甚至我们还可以到代理层改变我们http接口信息。java的Proxy也是如此,可以在访问真正类的时候做一些前置和后置的统一工作。
JDK的动态Proxy实现
比如有个相亲代理机构,java程序员们通过这个代理机构来找老婆,java程序员有个需求,对象必须是A照杯 的才行,这些就可以统一交给相亲代理机构来做,自己等结果就行了。
代码实现
public class Main { // 相亲代理机构 static class FindWomanProxy implements InvocationHandler{ // 被代理的对象 private FindWoman woman; public FindWomanProxy(FindWoman woman) { this.woman = woman ; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //在真实的对象执行之前我们可以添加自己的操作 System.out.println("相亲代理正在筛选A照杯的女士"); // 筛选 完成 交给 程序员去消费相亲 return method.invoke(woman, args); } } // 相亲 接口 static interface FindWoman { public void find(); } // java 程序员相亲 static class JavaFindWoman implements FindWoman{ @Override public void find() { System.err.println("java程序员相亲 "); } } public static void main(String[] args) { // 构造相亲代理 , 把java程序员传进去 FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman()); // 机构产出的相亲代理对象,并非传入的 JavaFindWoman 。 FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy); findWoman.find(); } }代码解释: java程序员 通过相亲代理机构(FindWomanProxy) 去完成相亲这件事(FindWoman)。代理机构 产出代理对象,然后调用代理对象的相亲方法,会执行筛选A照杯 ,然后java程序员真正进行相亲。
代理的好处
以后PHP程序员相亲,只要在相亲机构中传入PHP程序员就行了。
FindWomanProxy proxy = new FindWomanProxy(new PhpFindWoman());而且我们要更改 相亲机构 的 筛选 "照杯" 算法也很简单,统一就改了。还有一个很重要的好处:
发现没有,我们JavaFindWoman类是不是 很干净, 没有丝毫的 筛选A照杯 算法 代码。也就是说 可以 实现无侵入式的代码扩展 。
我很好奇,那个 Proxy.newProxyInstance 方法 是是怎么动态生成代理对象的?生成的代理对象字节码又是什么样子? 于是我继续进行研究。
Proxy.newProxyInstance 实现原理
通过调式jdk源码,发现了内部用了缓存来缓存生成的class,不是每一次都生成,最终生成class的代码在apply里面(缓存部分的我就不讲了)
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // 生成的class 前缀 private static final String proxyClassNamePrefix = "$Proxy"; // 名字的自增 标识 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { Class<?> interfaceClass = null; try { // 加载传入的接口 也就是我们上面的相亲接口FindWoman.class interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } if (!interfaceClass.isInterface()) { // 不是接口抛出异常。 所以JDK只能代理接口 throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } ///////////////这段代码 就是为了产生 proxyPkg包名 String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.getName(); int n = name.lastIndexOf(.); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } long num = nextUniqueNumber.getAndIncrement(); // proxyPkg 为 传入接口的 所在包名称 //proxyClassNamePrefix:固定值$Proxy // num : 自增 // 生成的代理类名称 包名.$Proxy0 String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * sun.misc.ProxyGenerator 工具 生成类 的字节流 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { // 将class字节流 加载到jvm , 从而生成class return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len); }从上面看出 其实也是借助了 sun.misc.ProxyGenerator 工具 生成class字节流,然后通过native方法 defineClass0 加载到jvm生成class的。而且上面判断了只能代理接口。
上面拿到class 之后 ,然后通过反射,就构造出了代理对象了
// cl为上面产生的 class final Constructor<?> cons = cl.getConstructor(constructorParams); // 反射 构造实例 return cons.newInstance(new Object[]{h});现在我觉得我最关注的就是这个class 里面内容到底是什么? jdk提供一个参数让我们把class的文件download出来
public static void main(String[] args) throws IOException { //生成$Proxy0的class文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 构造相亲代理 , 把java程序员传进去 FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman()); FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy); findWoman.find(); }sun.misc.ProxyGenerator.saveGeneratedFiles 设置为true后,会生成class文件
我们通过反编译工具 jd-gui 就可以看到 class内容
import debug_jdk8.; import debug_jdk8.Main; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; final class Proxy0 extends Proxy implements Main.FindWoman { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final boolean equals(Object paramObject) { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void find() { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("debug_jdk8.Main$FindWoman").getMethod("find", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } } }内部确实很用心, 把toString,hashCode,equals方法都代理了。也继承了 Proxy类,实现了我们的接口。Proxy类:
public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { this.h = h; } ... }我们就关注下我们代理 的 find 方法吧
public final void find() { try { // h : InvocationHandler 也就是我们的相亲代理机构 FindWomanProxy // m3 this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException error) { throw null; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { // 代理的接口的 find 方法 Method对象 m3 = Class.forName("debug_jdk8.Main$FindWoman").getMethod("find", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); }也就是回调了我们的代理机构的 InvocationHandler 方法了
这样当调用代理对象的find 方法时 , 也就回调到上面这个红色的方法了。到此,面纱已经解开。我们在来自己实现一个简易的JDK动态Proxy。
自己实现JDK 动态Proxy
我们不用 sun.misc.ProxyGenerator 来生class了,我们用 Javassist 技术动态生成class。
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。它可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。
算了,下面还有很多内容要讲,我另起炉灶吧,要想自己实现一个JDK的动态代理,请您移步到这。
罗政:自己实现Java 动态代理 Proxy0 赞同 · 0 评论文章强烈推荐一个 进阶 JAVA架构师 的博客
Java架构师修炼githubs.xyz/本文完~