动态代理虽不常自己实现,但在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());
}
}
}

  

最新文章

  1. 求n个排序链表的交集
  2. Java学习-004-传世经典Helloworld
  3. PHP 网站保存快捷方式的实现代码
  4. class属性添加多个类
  5. 如何将域中的AD数据导入SharePoint
  6. Java编程思想-泛型-泛型方法
  7. Android 监控网络状态
  8. Lua多重继承
  9. add,update,list.jsp源码
  10. 使用可以为 null 的类型
  11. 201521123107 《Java程序设计》第10周学习总结
  12. Codeforces Round #484 (Div. 2)Cut 'em all!(dfs)
  13. 牛客练习赛 A题 筱玛的快乐
  14. Windows PowerShell 入門(2)-基本操作編 2
  15. jQuery基础与JavaScript与CSS交互-第五章
  16. SpringBoot-整合lombok
  17. sam9260 adc module
  18. R语言NULL、NA、0
  19. 基础篇:3.4)3d模型绘制的好坏会影响产品合格率(注意点)
  20. 记录: 一次解决整型溢出攻击(使用scala,隐式转换)

热门文章

  1. Collections.sort排序
  2. Devexpress之LayoutControl的使用及其控件布局设计
  3. 使用python调用其他脚本
  4. vue-router 路由跳转:和name配对的是params,和path配对的是query
  5. vn.trader的Ubuntu运行环境搭建教程
  6. [ZZ] Valse 2017 | 生成对抗网络(GAN)研究年度进展评述
  7. ubuntu ssh
  8. 工控随笔_20_西门子_WinCC的VBS脚本_09_常量和流程控制_02
  9. bootice-diskinfo参数
  10. 解决WIN7第一次开机冷启动QQ未响应的办法