Java JDK动态代理解析
动态代理虽不常自己实现,但在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());
}
}
}
Java JDK动态代理解析的更多相关文章
- Java JDK 动态代理实现和代码分析
JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...
- Java JDK 动态代理使用及实现原理分析
转载:http://blog.csdn.net/jiankunking 一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...
- java jdk动态代理模式举例浅析
代理模式概述 代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色. java中常用的动态代理模式为jdk动态代理和c ...
- java jdk动态代理学习记录
转载自: https://www.jianshu.com/p/3616c70cb37b JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过 ...
- Java,JDK动态代理的原理分析
1. 代理基本概念: 以下是代理概念的百度解释:代理(百度百科) 总之一句话:三个元素,数据--->代理对象--->真实对象:复杂一点的可以理解为五个元素:输入数据--->代理对象- ...
- java jdk动态代理
在面试的时候面试题里有一道jdk的动态代理是原理,并给一个事例直接写代码出来,现在再整理一下 jdk动态代理主要是想动态在代码中增加一些功能,不影响现有代码,实现动态代理需要做如下几个操作 1.首先必 ...
- java JDK动态代理的机制
一:前言 自己在稳固spring的一些特性的时候在网上看到了遮掩的一句话“利用接口的方式,spring aop将默认通过JDK的动态代理来实现代理类,不适用接口时spring aop将使用通过cgli ...
- Java JDK 动态代理(AOP)使用及实现原理分析
一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模式U ...
- java jdk动态代理(proxy)
1. 涉及主要jdk api java.lang.reflect.InvocationHandler: public interface InvocationHandler { /** * Proce ...
随机推荐
- python随笔--复习专用
<!doctype html> blockquote:first-child, #write > div:first-child, #write > figure:first- ...
- bond模式
1.mode=0(balance-rr)(平衡抡循环策略) 链路负载均衡,增加带宽,支持容错,一条链路故障会自动切换正常链路.交换机需要配置聚合口,思科叫port channel.特点:传输数据包顺序 ...
- 使用excel 数据透视表画图
① 打开Excel,选中需要制表的数据,点击“插入”->“数据透视表” ② 出现下列对话框,点击“确定” ③ 再新的“sheet”表内对“数 ...
- ecmobile-页面空白,也没异常提示,一般就是这个问题
分类页空白了://2018年09月07日14:55:21 四:页面空白 将ON_WILL_APPEAR中有关页面布局方法写在ON_DID_APPEAR方法中.例如:
- 第七届蓝桥杯省赛javaB组 第七题剪邮票
剪邮票 如[图1.jpg], 有12张连在一起的12生肖的邮票.现在你要从中剪下5张来,要求必须是连着的.(仅仅连接一个角不算相连)比如,[图2.jpg],[图3.jpg]中,粉红色所示部分就是合格的 ...
- docker:构建nginx+php-fpm镜像(一):构建nginx自启动镜像
步骤一:手动安装nginx环境,并记录全过程: #使用yum更新系统 yum -y update #下面编译安装tengine,查看有哪些包需要安装 #安装wget包,用于获取安装软件包 yum ...
- OpenCV中feature2D——BFMatcher和FlannBasedMatcher
作者:holybin 原文:https://blog.csdn.net/holybin/article/details/40926315 Brute Force匹配和FLANN匹配是opencv二维特 ...
- 老集群RAC双网卡绑定
老集群RAC双网卡绑定 作者:Eric 微信:loveoracle11g [root@db-rac02 network-scripts]# cat ifcfg-bond0 DEVICE=bond0 I ...
- Asp.NET 简易通用WebServices 附件服务
[toc] 总述: 用了很久的附件分离服务, .NET 2.0平台开始使用. 配置好服务后, 由调用端定义并管理目录级次. 调用端存储目录即可. 附件服务: 相应配置节点放入 web.confi ...
- IIS Express 域认证问题(https://stackoverflow.com/questions/4762538/iis-express-windows-authentication)
option-1: edit \My Documents\IISExpress\config\applicationhost.config file and enable windowsAuthent ...