Java JDK动态代理解析

java

  动态代理虽不常自己实现,但在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动态代理解析 的全部内容, 来源链接: utcz.com/z/394118.html

回到顶部