动态代理虽不常自己实现,但在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动态代理解析的更多相关文章

  1. Java JDK 动态代理实现和代码分析

    JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...

  2. Java JDK 动态代理使用及实现原理分析

    转载:http://blog.csdn.net/jiankunking   一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理 ...

  3. java jdk动态代理模式举例浅析

    代理模式概述 代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色. java中常用的动态代理模式为jdk动态代理和c ...

  4. java jdk动态代理学习记录

    转载自: https://www.jianshu.com/p/3616c70cb37b JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过 ...

  5. Java,JDK动态代理的原理分析

    1. 代理基本概念: 以下是代理概念的百度解释:代理(百度百科) 总之一句话:三个元素,数据--->代理对象--->真实对象:复杂一点的可以理解为五个元素:输入数据--->代理对象- ...

  6. java jdk动态代理

    在面试的时候面试题里有一道jdk的动态代理是原理,并给一个事例直接写代码出来,现在再整理一下 jdk动态代理主要是想动态在代码中增加一些功能,不影响现有代码,实现动态代理需要做如下几个操作 1.首先必 ...

  7. java JDK动态代理的机制

    一:前言 自己在稳固spring的一些特性的时候在网上看到了遮掩的一句话“利用接口的方式,spring aop将默认通过JDK的动态代理来实现代理类,不适用接口时spring aop将使用通过cgli ...

  8. Java JDK 动态代理(AOP)使用及实现原理分析

    一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模式U ...

  9. java jdk动态代理(proxy)

    1. 涉及主要jdk api java.lang.reflect.InvocationHandler: public interface InvocationHandler { /** * Proce ...

随机推荐

  1. python随笔--复习专用

    <!doctype html> blockquote:first-child, #write > div:first-child, #write > figure:first- ...

  2. bond模式

    1.mode=0(balance-rr)(平衡抡循环策略) 链路负载均衡,增加带宽,支持容错,一条链路故障会自动切换正常链路.交换机需要配置聚合口,思科叫port channel.特点:传输数据包顺序 ...

  3. 使用excel 数据透视表画图

    ①    打开Excel,选中需要制表的数据,点击“插入”->“数据透视表”          ②    出现下列对话框,点击“确定”          ③    再新的“sheet”表内对“数 ...

  4. ecmobile-页面空白,也没异常提示,一般就是这个问题

    分类页空白了://2018年09月07日14:55:21   四:页面空白 将ON_WILL_APPEAR中有关页面布局方法写在ON_DID_APPEAR方法中.例如:

  5. 第七届蓝桥杯省赛javaB组 第七题剪邮票

    剪邮票 如[图1.jpg], 有12张连在一起的12生肖的邮票.现在你要从中剪下5张来,要求必须是连着的.(仅仅连接一个角不算相连)比如,[图2.jpg],[图3.jpg]中,粉红色所示部分就是合格的 ...

  6. docker:构建nginx+php-fpm镜像(一):构建nginx自启动镜像

    步骤一:手动安装nginx环境,并记录全过程: #使用yum更新系统 yum -y update   #下面编译安装tengine,查看有哪些包需要安装 #安装wget包,用于获取安装软件包 yum ...

  7. OpenCV中feature2D——BFMatcher和FlannBasedMatcher

    作者:holybin 原文:https://blog.csdn.net/holybin/article/details/40926315 Brute Force匹配和FLANN匹配是opencv二维特 ...

  8. 老集群RAC双网卡绑定

    老集群RAC双网卡绑定 作者:Eric 微信:loveoracle11g [root@db-rac02 network-scripts]# cat ifcfg-bond0 DEVICE=bond0 I ...

  9. Asp.NET 简易通用WebServices 附件服务

    [toc] 总述: 用了很久的附件分离服务, .NET 2.0平台开始使用.  配置好服务后, 由调用端定义并管理目录级次.  调用端存储目录即可.  附件服务: 相应配置节点放入 web.confi ...

  10. IIS Express 域认证问题(https://stackoverflow.com/questions/4762538/iis-express-windows-authentication)

    option-1: edit \My Documents\IISExpress\config\applicationhost.config file and enable windowsAuthent ...