JAVA 动态代理两种实现方案 时间: 2018-03-16 18:14 分类: JAVA ####简介 在写实现代码之前,还是有必要知道一下代理模式的定义: > 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。[[百度百科](https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=aladdin "百度百科")] 上面的表述可能还是有些笼统,并没有说明代理模式的作用是干什么的。 代理模式可以这么理解:比如你是租房子的人,去租房的时候很多情况下是通过中介商租的房子,此时中介商就相当于代理模式中的代理对象,而房东才是真正出租房子的那个人。大家都知道,直接从房东那租房子一般比较便宜,因为通过中介商租的房子,还要交些钱给中介商,也就是说在执行房东租房的这个操作的时候,添加了额外的收费操作,这也就是代理模式的核心所在。 经常使用 Nginx 的朋友肯定知道,Nginx 有反代的功能,比如镜像一个网站,可以将别人的网站内容变成自己的,内容当然一般不是一层不变地给代理给过,而是经过反代的时候将别人网站的标题、内容做一些修改,这也就是 SEO 中常说的伪原创。 代理模式分为静态代理和动态代理两种。两者区别就是前者是预先将代理类编译好,而后者是在程序运行期间动态产生的。 ####通过 JAVA 自带的类实现 直接上代码: ```java public class JavaDynamicProxyTest { interface IHello { void sayHello(); } static class HelloImpl implements IHello { public void sayHello() { System.out.println("hello"); } } static class HelloProxy implements InvocationHandler { private Object obj; public Object bind(Object obj) { this.obj = obj; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before say hello."); Object result = method.invoke(obj, args); System.out.println("after say hello."); return result; } } public static void main(String[] args) { //用于生成动态产生的代理类 $Proxy0.class,但是我这里貌似不起作用,找不到在哪里,有人说建立 com/sun/proxy 文件夹,也不行 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); IHello hello = (IHello) new HelloProxy().bind(new HelloImpl()); hello.sayHello(); } } ``` 通过实现`InvocationHandler`接口来实现动态代理,关键代码是`Proxy.newProxyInstance`这个方法,它会根据 class 文件格式动态生成字节码,由于我这边测试时无法生成`$Proxy0.class`文件,所以也就看不到动态生成的代理类反编译后的源码,可以查阅相关资料去了解下到底生成了什么。 其实最后生成的代理对象最后是实现了要代理对象的父接口的,在上面的代码中也就是`IHello`接口,然后通过反射获取具体实现类`Hello`的所有方法,既然是实现了`IHello`接口,所以代码中也必须实现`sayHello`方法,只不过代码中是调用`InvocationHandler`的`invoke`方法,也就是执行的是我们的这块代码: ```java public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before say hello."); Object result = method.invoke(obj, args); System.out.println("after say hello."); return result; } ``` 不同的方法只是传的`Method`参数不同而已,这样一来就实现了动态代理。 ####CGLib实现动态代理 也是直接上代码: ```java public class CGLibDynamicProxy { static class Hello { public void sayHello() { System.out.println("hello"); } } static class HelloProxy implements MethodInterceptor { public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("before say hello."); Object result = methodProxy.invokeSuper(o, args); System.out.println("after say hello."); return result; } } public static void main(String[] args) throws IOException { //将生成的 class 全部输出到 D:\\class 目录下 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class"); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Hello.class); enhancer.setCallback(new HelloProxy()); Hello hello = (Hello) enhancer.create(); hello.sayHello(); //方便找到生成的代理类 class 文件 System.out.println(hello); } } ``` 输出结果: >CGLIB debugging enabled, writing to 'D:\class' before say hello. hello after say hello. before say hello. before say hello. after say hello. after say hello. test.proxy.CGLibDynamicProxy$Hello$\$EnhancerByCGLIB$$890d2219@6438a396 产生的 class 文件: * CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219$$FastClassByCGLIB$$9f9ceca1.class * CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219.class * CGLibDynamicProxy$Hello$$FastClassByCGLIB$$f4760ac2.class 可以发现第二个是我们的动态代理类,反编译代码如下: ```java public class CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219 extends Hello implements Factory { private boolean CGLIB$BOUND; public static Object CGLIB$FACTORY_DATA; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; private MethodInterceptor CGLIB$CALLBACK_0; private static Object CGLIB$CALLBACK_FILTER; private static final Method CGLIB$sayHello$0$Method; private static final MethodProxy CGLIB$sayHello$0$Proxy; private static final Object[] CGLIB$emptyArgs; private static final Method CGLIB$equals$1$Method; private static final MethodProxy CGLIB$equals$1$Proxy; private static final Method CGLIB$toString$2$Method; private static final MethodProxy CGLIB$toString$2$Proxy; private static final Method CGLIB$hashCode$3$Method; private static final MethodProxy CGLIB$hashCode$3$Proxy; private static final Method CGLIB$clone$4$Method; private static final MethodProxy CGLIB$clone$4$Proxy; static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("test.proxy.CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219"); Class var1; Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$equals$1$Method = var10000[0]; CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1"); CGLIB$toString$2$Method = var10000[1]; CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2"); CGLIB$hashCode$3$Method = var10000[2]; CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3"); CGLIB$clone$4$Method = var10000[3]; CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4"); CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("test.proxy.CGLibDynamicProxy$Hello")).getDeclaredMethods())[0]; CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0"); } final void CGLIB$sayHello$0() { super.sayHello(); } public final void sayHello() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy); } else { super.sayHello(); } } final boolean CGLIB$equals$1(Object var1) { return super.equals(var1); } public final boolean equals(Object var1) { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy); return var2 == null?false:((Boolean)var2).booleanValue(); } else { return super.equals(var1); } } final String CGLIB$toString$2() { return super.toString(); } public final String toString() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy):super.toString(); } final int CGLIB$hashCode$3() { return super.hashCode(); } public final int hashCode() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy); return var1 == null?0:((Number)var1).intValue(); } else { return super.hashCode(); } } final Object CGLIB$clone$4() throws CloneNotSupportedException { return super.clone(); } protected final Object clone() throws CloneNotSupportedException { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } return var10000 != null?var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy):super.clone(); } public static MethodProxy CGLIB$findMethodProxy(Signature var0) { String var10000 = var0.toString(); switch(var10000.hashCode()) { case -508378822: if(var10000.equals("clone()Ljava/lang/Object;")) { return CGLIB$clone$4$Proxy; } break; case 1535311470: if(var10000.equals("sayHello()V")) { return CGLIB$sayHello$0$Proxy; } break; case 1826985398: if(var10000.equals("equals(Ljava/lang/Object;)Z")) { return CGLIB$equals$1$Proxy; } break; case 1913648695: if(var10000.equals("toString()Ljava/lang/String;")) { return CGLIB$toString$2$Proxy; } break; case 1984935277: if(var10000.equals("hashCode()I")) { return CGLIB$hashCode$3$Proxy; } } return null; } public CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219() { CGLIB$BIND_CALLBACKS(this); } public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) { CGLIB$THREAD_CALLBACKS.set(var0); } public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) { CGLIB$STATIC_CALLBACKS = var0; } private static final void CGLIB$BIND_CALLBACKS(Object var0) { CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219 var1 = (CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219)var0; if(!var1.CGLIB$BOUND) { var1.CGLIB$BOUND = true; Object var10000 = CGLIB$THREAD_CALLBACKS.get(); if(var10000 == null) { var10000 = CGLIB$STATIC_CALLBACKS; if(CGLIB$STATIC_CALLBACKS == null) { return; } } var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; } } public Object newInstance(Callback[] var1) { CGLIB$SET_THREAD_CALLBACKS(var1); CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219 var10000 = new CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219(); CGLIB$SET_THREAD_CALLBACKS((Callback[])null); return var10000; } public Object newInstance(Callback var1) { CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1}); CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219 var10000 = new CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219(); CGLIB$SET_THREAD_CALLBACKS((Callback[])null); return var10000; } public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) { CGLIB$SET_THREAD_CALLBACKS(var3); CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219 var10000 = new CGLibDynamicProxy$Hello$$EnhancerByCGLIB$$890d2219; switch(var1.length) { case 0: var10000.(); CGLIB$SET_THREAD_CALLBACKS((Callback[])null); return var10000; default: throw new IllegalArgumentException("Constructor not found"); } } public Callback getCallback(int var1) { CGLIB$BIND_CALLBACKS(this); MethodInterceptor var10000; switch(var1) { case 0: var10000 = this.CGLIB$CALLBACK_0; break; default: var10000 = null; } return var10000; } public void setCallback(int var1, Callback var2) { switch(var1) { case 0: this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2; default: } } public Callback[] getCallbacks() { CGLIB$BIND_CALLBACKS(this); return new Callback[]{this.CGLIB$CALLBACK_0}; } public void setCallbacks(Callback[] var1) { this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0]; } static { CGLIB$STATICHOOK1(); } } ``` 可以看到,它继承了`Hello`类,这也是必须的,不然怎么将它强转为`Hello`对象呢(在测试代码中可以看到)。 既然是继承了`Hello`类,如果要代理`Hello`对象,那么必定需要重写`Hello`类的所有方法,在上面的代码中均可看到,像`equals`、`hashCode`这类暂且不看了,主要看下重写的`sayHello`方法。 ```java public final void sayHello() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy); } else { super.sayHello() } } ``` 可以看到最终调用的是`MethodInterceptor`的`intercept`方法,也就是上面我们实现`MethodInterceptor`接口所实现的方法,这样一来也就实现了动态代理。 再来看下前面有趣的输出`System.out.println(hello);`: >before say hello. before say hello. after say hello. after say hello. test.proxy.CGLibDynamicProxy$Hello$\$EnhancerByCGLIB$$890d2219@6438a396 本来我是想让它输出代理对象类的名称的,结果出现了上面有趣的现象。 找到`toString`方法: ```java public final String toString() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy):super.toString(); } ``` 可以看到确实也调用了`MethodInterceptor`的`intercept`方法,如果要找到原因,就得手动下断点一步一步地跟进查看执行的步骤。分析过程类的名字全是些带数字的,着实不太好看,下面说下我的理解吧: * 首先第一次调用`toString`,此时`var10000`对象不为 null,所以调用了它的`intercept`方法,也就是我们自己实现的那个方法,此时输出`before say hello.` * 之后继续执行`Object result = methodProxy.invokeSuper(o, args);`,注意,由于我们调用的是`toString`方法,所以肯定不会有`hello`输出了。 * 根据正常代理过程可知,此时`invokeSuper`是调用`Hello`类中的`toString`(根据前面 sayHello 可以推断出),但是`Hello`并未重写`Object`的`toString`方法,所以调用的是`Object`的`toString`,问题所在就在`Object`的`toString`方法里面,下面我们看下代码: ```java public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } ``` 代码非常简单,貌似没什么问题。但是仔细分析,可以发现问题就出在里面执行了`hashCode`方法,代理类继承了`Hello`类,并且重写了`Hello`类的所有方法,那么必然包括`hashCode`方法,所以调用`Object`的`toString`方法时里面调用的`hashCode`方法是代理类的方法,代码如下: ```java public final int hashCode() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy); return var1 == null?0:((Number)var1).intValue(); } else { return super.hashCode(); } } ``` 里面再次调用了`intercept`方法,也就是我们实现`MethodInterceptor`接口时实现的方法,即再次调用: ```java public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("before say hello."); Object result = methodProxy.invokeSuper(o, args); System.out.println("after say hello."); return result; } ``` 所以再次输出`before say hello.`,继续执行`Object result = methodProxy.invokeSuper(o, args);`,经过上面的`toString`分析,我们可以知道这里也是执行的`Object`类的`hashCode`方法,查看代码: ```java public native int hashCode(); ``` 是个`native`方法,执行结果就是个整数。然后继续输出`after say hello.`。 * 此时`toString`方法代码回到`intercept`方法中的``Object result = methodProxy.invokeSuper(o, args);`地方,继续往下执行,也就再次输出了`after say hello.`,然后将`Object`类的`toString`结果返回,最后输出`test.proxy.CGLibDynamicProxy$Hello$\$EnhancerByCGLIB$$890d2219@6438a396` 到此,分析结束,但是在断点调试的时候会发现,多输出了很多结果,比如输出结果如下: > CGLIB debugging enabled, writing to 'D:\class' before say hello. hello after say hello. before say hello. before say hello. after say hello. after say hello. before say hello. before say hello. after say hello. after say hello. test.proxy.CGLibDynamicProxy$Hello$\$EnhancerByCGLIB$$890d2219@2a2d45ba 有兴趣的朋友可以尝试找下原因,目前我还不清楚这是什么原因导致的。 ####总结 最后,对比下 JAVA 自带的类实现,与 CGLib 实现的区别:前者必须实现接口,后者可以不用实现。两者都是通过重写需代理类的方法来实现,都是运行时生成字节码生成对象。 使用场景:Spring 的 AOP、Struts 的拦截器等都有用到。可以用来加操作日志、管理事务、拦截器操作等。 ####补充 `methodProxy.invokeSuper`如何调用父类方法? 在反编译的代理类中可以发现,所有重写的方法都出现了两份,比如`sayHello`: ```java final void CGLIB$sayHello$0() { super.sayHello(); } public final void sayHello() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if(this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if(var10000 != null) { var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy); } else { super.sayHello(); } } ``` 所以猜测,`methodProxy.invokeSuper`调用父类`sayHello`就是调用代理类的`CGLIB$sayHello$0()`方法。 标签: 动态代理 DynamicProxy
不错