zoukankan      html  css  js  c++  java
  • JDK的动态代理机制

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

    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          Adder calc = new AdderImpl();
    08          int result = calc.add( 1 , 2 );
    09          System.out.println( "The result is " + result);
    10      }
    11 }

    很显然,控制台会输出:

    1 The result is 3

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

    1 Proxy: invoke add() at 2009-12-16 17:18:06
    2 The result is 3

    动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口 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       * @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl
    11       * @param method 调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例
    12       * @param args 调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数
    13       * @return 使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值
    14       */
    15      @Override
    16      public Object invoke(Object proxy, Method method, Object[] args)
    17              throws Throwable {
    18          // ...
    19      }
    20 }

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

    1 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中加入以下这么一段:

    1 // 被代理的对象
    2 private Object target;
    3  
    4 public AdderHandler(Object target) {
    5      this .target = target;
    6 }

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

    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 Object target;
    12  
    13      public AdderHandler(Object target) {
    14          this .target = target;
    15      }
    16       
    17      @Override
    18      public Object invoke(Object proxy, Method method, Object[] args)
    19              throws Throwable {
    20          // 调用被代理对象的方法并得到返回值
    21          Object returnValue = 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   * @param loader 类加载器,用于加载生成的代理类。
    03   * @param interfaces 需要代理的接口。这些接口的所有方法都会被代理。
    04   * @param h 第一步中我们建立的Handler类的实例。
    05   * @return 代理对象,实现了所有要代理的接口。
    06   */
    07 public static Object newProxyInstance(ClassLoader loader,
    08            Class<?>[] interfaces,
    09            InvocationHandler h)
    10 throws IllegalArgumentException

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

    01 // 模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。
    02  
    03 class AdderProxy extends Proxy implements Adder {
    04      protected AdderProxy(InvocationHandler h) {
    05          super (h);
    06      }
    07  
    08      @Override
    09      public int add( int a, int b) {
    10          try {
    11              Method m = 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 (Throwable e) {
    15              throw new RuntimeException(e);
    16          }
    17      }
    18 }

    据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api 生成;一般情况下应该是直接生成了字节码。然后,使用你提供的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          Adder calc = new AdderImpl();
    11           
    12          // 类加载器
    13          ClassLoader loader = Test. class .getClassLoader();
    14          // 需要代理的接口
    15          Class[] interfaces = {Adder. class };
    16          // 方法调用处理器,保存实际的AdderImpl的引用
    17          InvocationHandler h = new AdderHandler(calc);
    18          // 为calc加上代理
    19          calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h);
    20           
    21          /* 什么?你说还有别的需求? */
    22          // 另一个处理器,保存前处理器的引用
    23          // InvocationHandler h2 = new XXOOHandler(h);
    24          // 再加代理
    25          // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2);
    26           
    27          int result = calc.add( 1 , 2 );
    28          System.out.println( "The result is " + result);
    29      }
    30 }

    输出结果会是什么呢?

    1 Proxy: invoke add() at 2009-12-16 18:21:33
    2 The result is 3

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

    target完全可以在代理类中生成。
    实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:return xxx.invoke(ooo);

    不过这么写也有他的理由,target自己管理,方法你爱调不调 ﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。

    作者:Angelo Lee
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    点语法
    第一个OC的类
    gitlab教程
    php接收post的json数组
    phpstorm10.0.2三月22号补丁原来的网址被封了
    冰点下载器在转换pdf的时候出现停止工作
    乱码问题
    有用的网址(php)
    ubuntu16.04安装mysql5.6
    MapUtils常用方法
  • 原文地址:https://www.cnblogs.com/yefengmeander/p/2887817.html
Copyright © 2011-2022 走看看