JDK的动态代理机制
2010-05-31 10:27
363 查看
jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。
viewsource
print
?
viewsource
print
?
viewsource
print
?
viewsource
print
?
很显然,控制台会输出:
viewsource
print
?
然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:
viewsource
print
?
动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口
java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生
成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。
viewsource
print
?
使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用adder.add()方法时,实际执行的将是invoke()。所
以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这
时我们马上会想到这样做:
viewsource
print
?
如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后
adder引用所指向的对象。由于我们调用了adder.add(1,
2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy,
args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为
MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:
viewsource
print
?
喜欢的话还可以加上getter/setter。接着,invoke()就可以这么用了:
viewsource
print
?
viewsource
print
?
这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码类似于这样:
viewsource
print
?
据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api
生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。
看明白这个方法后,我们来改造一下main()方法。
viewsource
print
?
输出结果会是什么呢?
viewsource
print
?
对比一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒:
target完全可以在代理类中生成。
实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:returnxxx.invoke(ooo);
不过这么写也有他的理由,target自己管理,方法你爱调不调﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。
1 | //Adder.java |
2 |
3 | package test; |
4 |
5 | public interface Adder{ |
6 | int add( int a, int b); |
7 | } |
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 | } |
1 | 现在我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。 |
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 | } |
1 | Theresultis3 |
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,需要重写的方法只有一个: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 | } |
以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这
时我们马上会想到这样做:
1 | ObjectreturnValue=method.invoke(proxy,args); |
adder引用所指向的对象。由于我们调用了adder.add(1,
2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy,
args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为
MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:
1 | //被代理的对象 |
2 | private Objecttarget; |
3 |
4 | public AdderHandler(Objecttarget){ |
5 | this .target=target; |
6 | } |
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()方法就可以生成一个代理对象。把这个方法的签名拿出来:01 | /** |
02 | *@paramloader类加载器,用于加载生成的代理类。 |
03 | *@paraminterfaces需要代理的接口。这些接口的所有方法都会被代理。 |
04 | *@paramh第一步中我们建立的Handler类的实例。 |
05 | *@return代理对象,实现了所有要代理的接口。 |
06 | */ |
07 | public static ObjectnewProxyInstance(ClassLoaderloader, |
08 | Class<?>[]interfaces, |
09 | InvocationHandlerh) |
10 | throws IllegalArgumentException |
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 | } |
生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。
看明白这个方法后,我们来改造一下main()方法。
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 | } |
1 | Proxy:invokeadd()at2009-12-1618:21:33 |
2 | Theresultis3 |
target完全可以在代理类中生成。
实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:returnxxx.invoke(ooo);
不过这么写也有他的理由,target自己管理,方法你爱调不调﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。
相关文章推荐
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- JDK的动态代理机制
- Java动态代理机制原理详解(JDK 和CGLIB,Javassist,ASM)
- Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- JDK的动态代理机制
- 深入理解JDK动态代理机制
- 学习JDK与CGLib两种动态代理机制
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- 深入理解JAVA JDK动态代理机制
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- JDK的动态代理机制
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
- Java动态代理机制介绍(jdk和cglib的区别)
- 深入理解JDK动态代理机制
- Java动态代理机制详解(JDK 和CGLIB)
- JDK动态代理之WeakCache缓存的实现机制
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)