动态代理虽不常自己实现,但在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. java 线程理解

    import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util. ...

  2. 解决ubuntu输入正确用户密码重新跳到无法登录

    解决方法:我们需要将.Xauthority的拥有者改为登陆用户(或者干脆将.Xauthority删除,此法转自网上,本人未验证)开机后在登陆界面按下shift + ctrl + F1进入tty命令行终 ...

  3. SSM学习笔记

    Spring MVC[入门]就这一篇! https://www.jianshu.com/p/91a2d0a1e45a SpringMVC非注解方式和注解方式不能同时使用.注解方式只需要配一句话就行了d ...

  4. apache做反向代理

    实验目的 通过apache实现反向代理的功能,类似nginx反向代理和haproxy反向代理 环境准备 逻辑架构如下 前端是apche服务器,监听80端口,后端有两台web服务器,分别是node1和n ...

  5. 分布式ID生成策略

    策略一.UUID 策略二.数据库自增序列 策略三.snowflake算法 策略四.基于redis自增 思路:利用增长计数API,业务系统在自增长的基础上,配合其他信息组装成为一个唯一ID. redis ...

  6. MySQL更改命令行默认分隔符

    MySQL命令行默认语句分隔符为分号  ; 使用DELIMITER命令可以更改默认分隔符 mysql> DELIMITER   // 将默认分割符改为  //

  7. Centos6.9部署Gitlab-11.9.8并汉化

    Git 是一种分布式的代码版本管理系统,git在工作时可以不用时刻依赖后台服务器,在本地电脑上就可以管理版本控制,但是在需要协同开发时就必须要使用后台服务器了,目前互联网上有github,码云这样的远 ...

  8. jquery 获取name一样的值

    $("input[name=test]").map(function(){return this.value;}).get().join(",")

  9. JSP学习1---创建一个简单的jsp程序

    一.新建一个“Dynamic Web Project”动态Web项目 1.1输入项目名称 Project1,在Dynamic Web module version(动态Web模块版本),选择3.0(注 ...

  10. MySQL报错ERROR 1558 (HY000): Column count of mysql.user is wrong.

    MySQL报错ERROR 1558 (HY000): Column count of mysql.user is wrong. 1.今天在使用MySQL创建数据库时出现如下报错: mysql> ...