您的位置:首页 > 编程语言 > Java开发

JDK的动态代理机制

2010-05-31 10:27 363 查看
jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。

viewsource

print
?

1

//Adder.java

2

3

package

test;

4

5

public

interface

Adder{

6


int

add(

int

a,

int

b);

7

}

viewsource

print
?

01

//AdderImpl.java

02

03

package

test;

04

05

public

class

AdderImpl

implements

Adder{

06


@Override

07


public

int

add(

int

a,

int

b){

08


return

a+b;

09


}

10

}

viewsource

print
?

1

现在我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。

viewsource

print
?

01

//Test.java

02

03

package

test;

04

05

public

class

Test{

06


public

static

void

main(String[]args)

throws

Exception{

07


Addercalc=

new

AdderImpl();

08


int

result=calc.add(

1

,

2

);

09


System.out.println(

"Theresultis"

+result);

10


}

11

}

很显然,控制台会输出:

viewsource

print
?

1

Theresultis3

然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:

viewsource

print
?

1

Proxy:invokeadd()at2009-12-1617:18:06

2

Theresultis3

动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口
java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生
成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。

第一步:实现InvokationHandler,定义调用方法时应该执行的动作。

自定义一个类MyHandler实现接口java.lang.reflect.InvokationHandler,需要重写的方法只有一个:

viewsource

print
?

01

//AdderHandler.java

02

03

package

test;

04

05

import

java.lang.reflect.InvocationHandler;

06

import

java.lang.reflect.Method;

07

08

class

AdderHandler

implements

InvocationHandler{

09


/**

10


*@paramproxy接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl

11


*@parammethod调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例

12


*@paramargs调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数

13


*@return使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值

14


*/

15


@Override

16


public

Objectinvoke(Objectproxy,Methodmethod,Object[]args)

17


throws

Throwable{

18


//...

19


}

20

}

使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用adder.add()方法时,实际执行的将是invoke()。所
以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这
时我们马上会想到这样做:

viewsource

print
?

1

ObjectreturnValue=method.invoke(proxy,args);

如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后
adder引用所指向的对象。由于我们调用了adder.add(1,
2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy,
args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为
MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:

viewsource

print
?

1

//被代理的对象

2

private

Objecttarget;

3

4

public

AdderHandler(Objecttarget){

5


this

.target=target;

6

}

喜欢的话还可以加上getter/setter。接着,invoke()就可以这么用了:

viewsource

print
?

01

//AdderHandler.java

02

03

package

test;

04

05

import

java.lang.reflect.InvocationHandler;

06

import

java.lang.reflect.Method;

07

import

java.util.Date;

08

09

class

AdderHandler

implements

InvocationHandler{

10


//被代理的对象

11


private

Objecttarget;

12

13


public

AdderHandler(Objecttarget){

14


this

.target=target;

15


}

16


17


@Override

18


public

Objectinvoke(Objectproxy,Methodmethod,Object[]args)

19


throws

Throwable{

20


//调用被代理对象的方法并得到返回值

21


ObjectreturnValue=method.invoke(target,args);

22


//调用方法前后都可以加入一些其他的逻辑

23


System.out.println(

"Proxy:invoke"

+method.getName()+

"()at"

+

new

Date().toLocaleString());

24


//可以返回任何想要返回的值

25


return

returnValue;

26


}

27

}

第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。

使用newProxyInstance()方法就可以生成一个代理对象。把这个方法的签名拿出来:

viewsource

print
?

01

/**

02


*@paramloader类加载器,用于加载生成的代理类。

03


*@paraminterfaces需要代理的接口。这些接口的所有方法都会被代理。

04


*@paramh第一步中我们建立的Handler类的实例。

05


*@return代理对象,实现了所有要代理的接口。

06


*/

07

public

static

ObjectnewProxyInstance(ClassLoaderloader,

08


Class<?>[]interfaces,

09


InvocationHandlerh)

10

throws

IllegalArgumentException

这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码类似于这样:

viewsource

print
?

01

//模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。

02

03

class

AdderProxy

extends

Proxy

implements

Adder{

04


protected

AdderProxy(InvocationHandlerh){

05


super

(h);

06


}

07

08


@Override

09


public

int

add(

int

a,

int

b){

10


try

{

11


Methodm=Adder.

class

.getMethod(

"add"

,

new

Class[]{

int

.

class

,

int

.

class

});

12


Object[]args={a,b};

13


return

(Integer)h.invoke(

this

,m,args);

14


}

catch

(Throwablee){

15


throw

new

RuntimeException(e);

16


}

17


}

18

}

据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api
生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。

看明白这个方法后,我们来改造一下main()方法。

viewsource

print
?

01

//Test.java

02

03

package

test;

04

05

import

java.lang.reflect.InvocationHandler;

06

import

java.lang.reflect.Proxy;

07

08

public

class

Test{

09


public

static

void

main(String[]args)

throws

Exception{

10


Addercalc=

new

AdderImpl();

11


12


//类加载器

13


ClassLoaderloader=Test.

class

.getClassLoader();

14


//需要代理的接口

15


Class[]interfaces={Adder.

class

};

16


//方法调用处理器,保存实际的AdderImpl的引用

17


InvocationHandlerh=

new

AdderHandler(calc);

18


//为calc加上代理

19


calc=(Adder)Proxy.newProxyInstance(loader,interfaces,h);

20


21


/*什么?你说还有别的需求?*/

22


//另一个处理器,保存前处理器的引用

23


//InvocationHandlerh2=newXXOOHandler(h);

24


//再加代理

25


//calc=(Adder)Proxy.newProxyInstance(loader,interfaces,h2);

26


27


int

result=calc.add(

1

,

2

);

28


System.out.println(

"Theresultis"

+result);

29


}

30

}

输出结果会是什么呢?

viewsource

print
?

1

Proxy:invokeadd()at2009-12-1618:21:33

2

Theresultis3

对比一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒:

target完全可以在代理类中生成。

实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:returnxxx.invoke(ooo);

不过这么写也有他的理由,target自己管理,方法你爱调不调﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: