动态代理-->jdk-api

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

Java代码 收藏代码
  1. packagetest;
  2. publicinterfaceAdder{
  3. intadd(inta,intb);
  4. }

// AdderImpl.java

Java代码 收藏代码
  1. packagetest;
  2. publicclassAdderImplimplementsAdder{
  3. @Override
  4. publicintadd(inta,intb){
  5. returna+b;
  6. }
  7. }

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

Java代码 收藏代码
  1. packagetest;
  2. publicclassTest{
  3. publicstaticvoidmain(String[]args)throwsException{
  4. Addercalc=newAdderImpl();
  5. intresult=calc.add(1,2);
  6. System.out.println("Theresultis"+result);
  7. }
  8. }

很显然,控制台会输出:
The result is 3
然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:
Proxy: invoke add() at 2009-12-16 17:18:06
The result is 3

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

Java代码 收藏代码
  1. packagetest;
  2. importjava.lang.reflect.InvocationHandler;
  3. importjava.lang.reflect.Method;
  4. classAdderHandlerimplementsInvocationHandler{
  5. /**
  6. *@paramproxy接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl
  7. *@parammethod调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例
  8. *@paramargs调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数
  9. *@return使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值
  10. */
  11. @Override
  12. publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
  13. throwsThrowable{
  14. //...
  15. }
  16. }

使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用 adder.add()方法时,实际执行的将是invoke()。所以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这时我们马上会想到这样做:
Object returnValue = method.invoke(proxy, args);
如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后 adder引用所指向的对象。由于我们调用了adder.add(1, 2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy, args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为 MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:

Java代码 收藏代码
  1. //被代理的对象
  2. privateObjecttarget;
  3. publicAdderHandler(Objecttarget){
  4. this.target=target;
  5. }

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

// AdderHandler.java

Java代码 收藏代码
  1. packagetest;
  2. importjava.lang.reflect.InvocationHandler;
  3. importjava.lang.reflect.Method;
  4. importjava.util.Date;
  5. classAdderHandlerimplementsInvocationHandler{
  6. //被代理的对象
  7. privateObjecttarget;
  8. publicAdderHandler(Objecttarget){
  9. this.target=target;
  10. }
  11. @Override
  12. publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
  13. throwsThrowable{
  14. //调用被代理对象的方法并得到返回值
  15. ObjectreturnValue=method.invoke(target,args);
  16. //调用方法前后都可以加入一些其他的逻辑
  17. System.out.println("Proxy:invoke"+method.getName()+"()at"+newDate().toLocaleString());
  18. //可以返回任何想要返回的值
  19. returnreturnValue;
  20. }
  21. }

第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。
使用newProxyInstance()方法就可以生成一个代理对象。把这个方法的签名拿出来:

Java代码 收藏代码
  1. /**
  2. *@paramloader类加载器,用于加载生成的代理类。
  3. *@paraminterfaces需要代理的接口。这些接口的所有方法都会被代理。
  4. *@paramh第一步中我们建立的Handler类的实例。
  5. *@return代理对象,实现了所有要代理的接口。
  6. */
  7. publicstaticObjectnewProxyInstance(ClassLoaderloader,
  8. Class<?>[]interfaces,
  9. InvocationHandlerh)
  10. throwsIllegalArgumentException

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

Java代码 收藏代码
  1. //模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。
  2. classAdderProxyextendsProxyimplementsAdder{
  3. protectedAdderProxy(InvocationHandlerh){
  4. super(h);
  5. }
  6. @Override
  7. publicintadd(inta,intb){
  8. try{
  9. Methodm=Adder.class.getMethod("add",newClass[]{int.class,int.class});
  10. Object[]args={a,b};
  11. return(Integer)h.invoke(this,m,args);
  12. }catch(Throwablee){
  13. thrownewRuntimeException(e);
  14. }
  15. }
  16. }

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

Java代码 收藏代码
  1. packagetest;
  2. importjava.lang.reflect.InvocationHandler;
  3. importjava.lang.reflect.Proxy;
  4. publicclassTest{
  5. publicstaticvoidmain(String[]args)throwsException{
  6. Addercalc=newAdderImpl();
  7. //类加载器
  8. ClassLoaderloader=Test.class.getClassLoader();
  9. //需要代理的接口
  10. Class[]interfaces={Adder.class};
  11. //方法调用处理器,保存实际的AdderImpl的引用
  12. InvocationHandlerh=newAdderHandler(calc);
  13. //为calc加上代理
  14. calc=(Adder)Proxy.newProxyInstance(loader,interfaces,h);
  15. /*什么?你说还有别的需求?*/
  16. //另一个处理器,保存前处理器的引用
  17. //InvocationHandlerh2=newXXOOHandler(h);
  18. //再加代理
  19. //calc=(Adder)Proxy.newProxyInstance(loader,interfaces,h2);
  20. intresult=calc.add(1,2);
  21. System.out.println("Theresultis"+result);
  22. }
  23. }

输出结果会是什么呢?
Proxy: invoke add() at 2009-12-16 18:21:33
The result is 3

对比一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒:target完全可以在代理类中生成。
实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:return xxx.invoke(ooo);不过这么写也有他的理由,target自己管理,方法你爱调不调 ﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值