动态代理虽不常自己实现,但在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. 配置django项目总结

    1.在django项目应用文件夹中的models.py文件中导入(1)from django.db import models(2)建立需要的映射的类名和属性类型也就是数据库中的表名和字段名 2.在s ...

  2. 12 个 JS 技巧

    1. 过滤唯一值 ES6 引入了 Set 对象和延展(spread)语法…,我们可以用它们来创建一个只包含唯一值的数组. 复制代码     const array = [1, 1, 2, 3, 5, ...

  3. Pyhon 逻辑运算符

  4. Git与Bitbucket配合使用

    1 , 简介 Git : Git是目前世界上最先进的分布式版本控制系统 Bitbucket : BitBucket 是一家代码托管网站 , 类似与GitHub , 不同的是GitHub更专注于开源 , ...

  5. python ctypes库3_如何传递并返回一个数组

    可以将数组指针传递给dll,但无法返回数组指针,python中没有对应的数组指针类型. 如果需要返回数组,需借助结构体. 参考ctypes官方文档: https://docs.python.org/3 ...

  6. Fibonacci_array

    重新开始学习C&C++ Courage is resistance to fear, mastery of fear, not abscence of fear //斐波那契数列 Fibona ...

  7. flutter mac 下安装

  8. 借助微软提供的url重写类库URLRewriter.dll(1.0)实现程序自动二级域名,域名需要泛解析

    二级域名和系统中会员帐号自动关联,也就是系统中注册一个会员,会员自动就可以通过二级域名来访问,比如我的帐号是zhangsan,我在morecoder.com注册后,访问zhangsan.morecod ...

  9. Webpack 模块处理

    webpack模块处理 1. ES6 静态Import ES6的import会被转化为commonjs格式或者是AMD格式,babel默认会把ES6的模块转化为commonjs规范的. import ...

  10. JNDI是什么

    参考文章:---为什么 追本溯源 http://www.cnblogs.com/xx0405/p/5332198.html jndi在Spring和tomcat下的使用: https://www.cn ...