动态代理虽不常自己实现,但在Spring或MyBatis中都有重要应用。动态代理的意义在于生成一个占位(又称代理对象),来代理真实对象,从而控制真实对象的访问。Spring常JDK和CGLIB动态代理技术。现就了解的JDK动态代理做个笔记。
先举个例子,然后再慢慢分析。也可以直接跳过例子,回过头再看。
比如十一的票好难买,乘客要买回家的票买不到,只好找黄牛做代理,让黄牛帮忙抢票。当然黄牛还要多收点钱,到卖票的时候,就帮乘客买票了。代码如下,先定义一个People接口,Passenger乘客类实现People接口,HuangNiuProxya黄牛类实现InvocationHandler接口。
//People接口,声明买票的功能 package proxytest; public interface People{ public People buyTickets(String trafficTool); }
//乘客类,实现People接口,和买票的函数 package proxytest; public class Passenger implements People{ public People buyTickets(String trafficTool) { System.out.println("I am a prssenger."); System.out.println("I buy tickets of the " + trafficTool); return this; } }
//黄牛类,实现InvocationHandler类,和invoke函数 package proxytest; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class HuangNiuProxy implements InvocationHandler{ //真实对象 private Object target = null; /** * 建议代理对象和真实对象代理关系,并返回代理对象 * @param target 真实对象 * @return 代理对象 */ public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /* * 代理方法逻辑 * @param proxy 代理对象 * @param method 当前调试方法 * @param args 当前方法参数 * @return 代理结束返回 * @throws Throwable 异常 */ public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{ System.out.println("I am a HuangNiu. I can buy tickets for passengers."); System.out.println("I am belong to " + this.getClass().getName()); System.out.println("I take more money."); System.out.println("Method: " + method); Object obj = method.invoke(target, args); System.out.println("I am a HuangNiu. I give passenger the tickets."); //可以返回代理对象 return proxy; //也可以返回method返回值 //return obj; } }
//测试类 package proxytest; public class TestProxy{ public static void main(String args[]) { HuangNiuProxy huangNiu = new HuangNiuProxy(); //绑定关系 People proxy = (People)huangNiu.bind(new Passenger()); System.out.println("proxy:" + proxy.getClass().getName()); //买票,先买火车票,再买汽车票 proxy.buyTickets("train").buyTickets("bus"); } }
//输出结果 proxy:com.sun.proxy.$Proxy0 I am a HuangNiu. I can buy tickets for passengers. I am belong to proxytest.HuangNiuProxy I take more money. Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String) I am a prssenger. I buy tickets of the train I am a HuangNiu. I give passenger the tickets. I am a HuangNiu. I can buy tickets for passengers. I am belong to proxytest.HuangNiuProxy I take more money. Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String) I am a prssenger. I buy tickets of the bus I am a HuangNiu. I give passenger the tickets.
从上面输出结果可以看到,黄牛收了更多的钱,帮乘客买了票,最后把票给了乘客,确实起到了代理的作用。
针对上面的代码及输出,我们提出几个问题,从问题入手,分析JDK动态代理:
1. 先声明一个People接口有什么用?
2. 为什么要实现InvocationHandler接口?
3. 测试类中的proxy是什么类型?
4. 测试类中的proxy为什么可以转换成People类型?
5. proxy.buyTickes函数是如何调用到invoke函数?
6. InvocationHandler接口的invoke函数要返回什么类型?
要回答上面几个问题,第三个问题好像是关键,因为涉及到的类的代码都写的明明白白,就它生成的不清不楚,但动态代理功能又是通过它调用。在上面的例子中,也打出了这个proxy实例的所属的类型,它是类型即不是Proxy类型,也不是我们声明的任何一个类型,而是$Proxy0。
System.out.println("proxy:" + proxy.getClass().getName()); //proxy:com.sun.proxy.$Proxy0
原因是通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,Proxy为中,最后一个数字表示对象的标号。既然是动态生成的,那必定有个性化的设置,否则,用静态代码就可以了。
我们通过定义一个输出.class文件的javaagent来输出编译运行过程中生成的.class文件,再反编译生成源代码,就可以看到$Proxy0的定义了。让我们稍做铺垫,看过源码之后,相信上面的问题就比较好解决了。为了不占篇幅,这段代码在最下面。只要看开头类的声明,构造函数,以及buyTickets函数和最下面的static静态代码段就可以了。
现在来解答一下上面的问题
1. 声明一个People接口有什么用?
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象,所以先定义接口。在代码后面的应用中,我们也看到了,生成代理对象时,在Proxy.newProxyInstance传了目录类的接口(target.getClass().getInterfaces()),从动态生成的$Proxy0的定义中可以看到,$Proxy0实现了这个接口。
2. 为什么要实现InvocationHandler接口?
InvocationHandler
是由代理实例的调用处理程序实现的接口 。
每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke
方法。
上面这段话是java API里的解释,约定了,代理实例(proxy)调用方法时,就会调用到InvocationHandler接口的invoke方法。那是如何实现的呢?
篇尾$Proxy0的代码给了我们答案。
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this)
在Proxy.newProxyInstance函数中传入了参数this,this就是InvocationHandler的一个实例,而在$Proxy0类中,这个InvocationHandler实例被传入$Proxy0的构造函数,$Proxy0的实例proxy就持有了这个InvocationHandler实例。由此可以调用InvocationHandler接口的invoke方法,至于invoke方法最后执行哪个method,是由入参决定的,这也是由$Proxy0的实例proxy控制的。
3. 测试类中的proxy是什么类型?
它是类型即不是Proxy类型,也不是我们声明的任何一个类型,而是$Proxy0。通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,并且命名方式都是这样的形式,以$开头,Proxy为中,最后一个数字表示对象的标号。
4. 测试类中的proxy为什么可以转换成People类型?
proxy是$Proxy0类型的实例,$Proxy0类型实现了People接口。该接口是通过Proxy.newProxyInstance传了目录类的接口target.getClass().getInterfaces())传入的。
5. proxy.buyTickes函数是如何调用到invoke函数?
proxy实例持有InvocationHandler接口的实例,从而可以调用InvocationHandler接口里的invoke方法。$Proxy0类中还声明了几个函数类型,通过给invoke函数传递不同的函数类型,来实现调用不同的功能。
6. invoke函数要返回什么类型?
在上面黄牛买票的例子中,invoke函数既可以返回代理类型(proxy),也可以返回method.invoke返回的类型。不同之处在于,返回代理类型,可以继续用代理类型操作,返回method.invode返回的类型,就是被代理对象函数里返回的类型。就上面例子买票的代码而言(如下),如果invoke函数返回代理类型,那么在代理买完火车票之后,会再代理买汽车票;如果invoke函数返回被代理对象返回的类型(返回的是passenger本身),那么passenger本身会再买汽车票,而没有再用到代理。
//买票,先买火车票,再买汽车票 proxy.buyTickets("train").buyTickets("bus");
//invoke函数返回proxy实例时,proxy会再代理买汽车票 proxy:com.sun.proxy.$Proxy0 I am a HuangNiu. I can buy tickets for passengers. I am belong to proxytest.HuangNiuProxy I take more money. Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String) I am a prssenger. I buy tickets of the train I am a HuangNiu. I give passenger the tickets. I am a HuangNiu. I can buy tickets for passengers. I am belong to proxytest.HuangNiuProxy I take more money. Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String) I am a prssenger. I buy tickets of the bus I am a HuangNiu. I give passenger the tickets.
//如果invoke函数返回的被代理实例函数返回的类型(passenger),那由passenger自己买票 proxy:com.sun.proxy.$Proxy0 I am a HuangNiu. I can buy tickets for passengers. I am belong to proxytest.HuangNiuProxy I take more money. Method: public abstract proxytest.People proxytest.People.buyTickets(java.lang.String) I am a prssenger. I buy tickets of the train I am a HuangNiu. I give passenger the tickets. I am a prssenger. I buy tickets of the bus
文中对应的代码见:https://github.com/zhaoshizi/JDKProxyTest,包括黄牛买票例子,$Proxy0.java源码,生成.class文件的javaagent.jar
要想输出输出的.class文件,在运行时加上jvm参数 -javaagent:XXX/jagent.jar
XXX为路径,.class文件生成在项目文件夹下的EXPORTED文件夹中
动态生成的$Proxy0类型的反编译的源代码:
package com.sun.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import proxytest.People; public final class $Proxy0 extends Proxy implements People { private static Method m1; private static Method m2; private static Method m3; 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 (...){ ...... } } public final String toString() { try{ return (String)this.h.invoke(this, m2, null); }catch (...){ ...... } } //$Proxy0类型中实现了与People接口中的buyTickets函数,并且调用了InvocaHandler的invoke函数 public final People buyTickets(String paramString) { try{ return (People)this.h.invoke(this, m3, new Object[] { paramString }); }catch (Error|RuntimeException localError){ throw localError; }catch (Throwable localThrowable){ throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try{ return ((Integer)this.h.invoke(this, m0, null)).intValue(); }catch (...){ ...... } } static { try{ m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); //m3被定义为buyTickes类函数类型 m3 = Class.forName("proxytest.People").getMethod("buyTickets", new Class[] { Class.forName("java.lang.String") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; }catch (NoSuchMethodException localNoSuchMethodException){ throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); }catch (ClassNotFoundException localClassNotFoundException){ throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }