zoukankan      html  css  js  c++  java
  • Java基础(十四)代理(Proxy)

      1.为什么要使用代理

      代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

      假设有一个表示接口的Class对象(有可能只包含一个接口),它的确切类型在编译时无法知道,如果想要根据这个Class对象来构造一个实现这些接口的类,就需要使用newInstance方法或者反射找出类的构造器,但是,不能实例化一个接口,需要在程序处于运行状态时定义一个新类。

      代理机制可以解决这个问题,代理类可以在运行时创建新的类,这样的代理类能够实现执行的接口。并且代理类具有指定接口中的全部方法以及Object类中的全部方法。

      2.创建代理对象

      要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。

      这个方法有三个参数:

    • ClassLoader loader:类加载器,用null表示默认的类加载器。
    • Class<?>[] interfaces:Class对象数组,每个元素都是需要实现的接口
    • InvocationHandler h:调用处理器
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

      调用处理器是实现了InvocationHandler接口的类的对象,这个接口中只有一个invoke方法,无论什么时候调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。

    public Object invoke(Object proxy, Method m, Object[] args)

      以一个例子说明:

    package proxy;
    
    import java.lang.reflect.*;
    import java.util.*;
    
    public class ProxyTest
    {
       public static void main(String[] args)
       {
          Object[] elements = new Object[1000];
          for (int i = 0; i < elements.length; i++)
          {
             Integer value = i + 1;
             InvocationHandler handler = new TraceHandler(value);
             Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class } , handler);
             elements[i] = proxy;
          }
    
          Integer key = new Random().nextInt(elements.length) + 1;
          System.out.println("key: " + key);
    
          int result = Arrays.binarySearch(elements, key);
          System.out.println("result: " + result);
    
          if (result >= 0) System.out.println(elements[result]);
       }
    }
    
    class TraceHandler implements InvocationHandler
    {
       private Object target;
    
       public TraceHandler(Object t)
       {
          target = t;
       }
    
       public Object invoke(Object proxy, Method m, Object[] args) throws Throwable
       {
          // print implicit argument
          System.out.print(target);
          // print method name
          System.out.print("." + m.getName() + "(");
          // print explicit arguments
          if (args != null)
          {
             for (int i = 0; i < args.length; i++)
             {
                System.out.print(args[i]);
                if (i < args.length - 1) System.out.print(", ");
             }
          }
          System.out.println(")");
    
          // invoke actual method
          return m.invoke(target, args);
       }
    }

      以一个例子来分析代码执行过程,首先for循环,创建了1000个Interger对象value,将这些value对象传递到处理器的构造函数中,然后构造一个代理类对象,将这些代理类对象放入到element对象数组中。然后构造一个随机的Integer对象,假设key值为104,然后Arrays.binarySearch(elements, key);在elements对象数组中寻找对象值为104的索引,这个时候就发生了一些很令人难人寻味的事情。

      这里就要看一下Arrays.binarySearch方法的具体实现:

        public static int binarySearch(Object[] a, Object key) {
            return binarySearch0(a, 0, a.length, key);
        }

     然后看一下binarySearch0方法的具体实现,注意参数传递关系,a是第一个参数,key是最后一个参数

        // Like public version, but without range checks.
        private static int binarySearch0(Object[] a, int fromIndex, int toIndex,
                                         Object key) {
            int low = fromIndex;
            int high = toIndex - 1;
    
            while (low <= high) {
                int mid = (low + high) >>> 1;
                @SuppressWarnings("rawtypes")
                Comparable midVal = (Comparable)a[mid];
                @SuppressWarnings("unchecked")
                int cmp = midVal.compareTo(key);
    
                if (cmp < 0)
                    low = mid + 1;
                else if (cmp > 0)
                    high = mid - 1;
                else
                    return mid; // key found
            }
            return -(low + 1);  // key not found.
        }

      注意这个方法中的下面的代码,同时注意参数对应关系

                Comparable midVal = (Comparable)a[mid];int cmp = midVal.compareTo(key);

      有点乱,将思路再次梳理一下:

      (1)Arrays.binarySearch(elements, key);其中,key是Integer对象104

      (2)调用binarySearch(elements,  104);返回binarySearch0(elements, 0, elements.length, 104);

      (3)然后开始调用binarySearch0(elements, 0, elements.length, 104);方法:mid=500,midVal是Object对象elements[500]采用强制类型转换得到的Comparable接口类型的对象,在int cmp = midVal.compareTo(104);这行代码执行的时候,由于数组中都是代理对象,而elements[500]也是(null, new Class[] { Comparable.class } , handler);且handler = new TraceHandler(value);中value是500对应的代理对象,当这个代理对象执行.compareTo(104)这个方法的时候,由于compareTo方法是Comparable接口中的唯一方法如果代理对象调用了这个方法,就会去执行hander调用处理器对象对应的invoke(Object proxy, Method m, Object[] args)方法,此时构造函数中的target就是value也就是500,elements[500].compareTo(104)对应到invoke方法中就是,proxy=elements[500]m=compareToargs={104},然后就简单了,首先打印500,然后打印.compareTo(,然后打印args中所有的对象即104,然后打印),最后返回的是m.invoke(target, args);对应着elements[500].compareTo(104);也就是又回到了原来的方法中...最后二分法结束之后返回的result的值才是索引值并且大于0,然后进入System.out.println(elements[result]);然后又开始了...又开始了,

      System.out.println的实现是:

        public void println(Object x) {
            String s = String.valueOf(x);
            synchronized (this) {
                print(s);
                newLine();
            }
        }

      String.valueOf的实现是:

        public static String valueOf(Object obj) {
            return (obj == null) ? "null" : obj.toString();
        }

      这里就看出问题来了,toString方法也会被重定向到调用处理器上,这是为什么呢,明明toString都不属于Comparable接口,为什么还是要调用处理器。这是因为,即使不属于Comparable接口,toString方法也被代理了,因此就会...

      总的输出为:

    key: 104
    500.compareTo(104)
    250.compareTo(104)
    125.compareTo(104)
    62.compareTo(104)
    93.compareTo(104)
    109.compareTo(104)
    101.compareTo(104)
    105.compareTo(104)
    103.compareTo(104)
    104.compareTo(104)
    result: 103
    104.toString()
    104

      3.代理类的特性

      下面就要解释一下上面的toString方法到底是怎么回事。

      所有的代理类都扩展于Proxy类,一个代理类只有一个实例域--调用处理器,它定义在Proxy的父类中。为了履行代理对象的职责,所需要的任何附加数据都必须存储在调用处理器中。例如,代理Comparable对象时,TranceHandler包装了实际的对象。

      所有的代理类都覆盖了Object类中的方法toString、equals和hashCode。如同所有的代理方法一样,这些方法仅仅调用了调用处理器的invoke。Object类中的其他方法没有被重新定义。

      对于特定的类加载器和预设的一组接口来说,只能有一个代理类。即如果使用同一个类加载器和接口数组调用两次newProxyInstance方法的话,那么只能够得到同一个类的两个对象。

      代理类一定是public和final,如果代理类实现的所有接口都是public,代理类就不属于某个特定的包;否则,所有非公有的接口都必须属于同一个包,同时,代理类与属于这个包。

      可以通过Proxy类中的isProxyClass方法检测一个特定的Class对象是否代表着一个代理类。

      代理类真的是很麻烦呀。

  • 相关阅读:
    shell 脚本学习笔记1
    手把手教你uiautomator_android自动化测试第一个示范
    ubuntu设置环境变量
    albert1017 Linux下压缩某个文件夹(文件夹打包)
    /proc/sysrq-trigger详解
    Android攻城狮学习笔记—入门篇二
    Android攻城狮学习笔记—入门篇一
    《移动App测试实战》读书笔记
    Linux-Ubuntu
    Android-应用性能测试
  • 原文地址:https://www.cnblogs.com/BigJunOba/p/9329680.html
Copyright © 2011-2022 走看看