动态代理

代理模式

为实际要访问的对象创建一个代理,客户不再直接访问原始对象,而是通过代理对象,间接访问原始对象,这样就可以控制客户对原始对象对访问,或者在访问原始对象前增加预处理等。代理模式常用的场景包括:

  1. 功能增强,在不修改原始对象接口的前提下增加新的功能。例如原先有一个FileOperator类,负责各种文件操作(打开,关闭,删除等等), 如果现在需要对于每个操作加上日志,怎么实现呢? 最简单的方法当然时直接修改FileOperator类,对每个操作加上日志,但是这种的缺点是:
    (1) 干扰FileOperator核心功能,FileOperator类添加了大量非核心代码,导致代码混乱,如果后续由大量额外功能,FileOperator会非常混乱,并且没法隔离不同的额外需求,例如有两个功能:(1)将log纪录到磁盘,(2)将log上传到服务器, 如果这两个功能只能2选1,那就非常麻烦了
    (2) 如果FileObserver来自于第三方库,可能无法修改
    (3) FileObserver来自于第三方,且可修改源码,这种情况如果将来需要合并新的代码,可能会出现大量到冲突
    因此通常都不会直接修改FileOperator类,而是通过代理的方式实现,创建一个代理类FileOperatorProxy(静态代理),并封装FileOprator类。这种方式非常像装饰者模式,主要区别在于,装饰者模式会定义统一的接口,装饰者必须实现被装饰者的所有接口,而静态代理只需要定义需要的接口,其他不需要访问的接口可以省略。

  2. 远程代理
    主要用于客户无法直接访问原始对象的情况,需要通过Proxy实现与远程对象的交互,例如Binder机制,客户端直接访问BinderProxy的接口,proxy通过Binder驱动与远端Binder对象进行交互。

静态代理

通过手动创建一个Proxy类,并且封装目标类,例如上述FileOperatorProxy。所有对FileOperator的访问都需要通过FileOpratorProxy预处理。静态代理最大的缺点在于对原始类对访问能力局限于Proxy类,如果想暴露原始类的所有接口,Proxy类就得定义原始类的所有接口的访问入口,如果接口很多,并且很多接口实际上是不需要预处理,则Proxy 类会写很多无用代码, 例如

  public class FileOperator {
        public void writeToFile() {
              //write content to file
        }
        public void getOperateCount() {
              //get operate count
        }
  }

实现一个Proxy 类,纪录log
public class FileOperatorProxy {
      private FileOperator target;
      public FileOperatorProxy(FileOperator target) {
              this.target = target;
      }
      public void writeToFile() {
            //print log
            target.writeToFile();
      }
      public int getOperateCount() {
            return target.getOperateCount();
      }
}

显示,FileOperatorProxy类的getOperateCount这个方法就很累赘,但是为了暴露FileOperator的getOperateCount方法,Proxy 类必须实现大量这样的无用接口,非常繁琐。这就有必要用到动态代理了。

动态代理

于静态代理的区别在于,静态代理类必须预先创建,而动态代理类是运行时根据原始类的接口(注意,必须是interface, 否则会抛异常)动态创建Proxy, 通过InvocationHandler接口,所有对Proxy对象对访问都统一调用InvocationHandler的invoke接口,这样,所有的增强操作都可以在invoke中完成,不需要增强的方法,直接调用原始对象的方法,因此动态代理的关键就在于实现InvocationHandler的invoke对象,对特定方法进行处理,其他方法不做任何处理:

public interface IFileOperator {
      void writeToFile();
      int getOperateCount();
}
public class FileOperator implements IFileOperator {
      @Override
      public void writeToFile() {
            //writeToFile
      }
      @Override
      public int getOperateCount() {
           //return operate count
      }
}
public class MyInvocationHandler implements InvocationHandler {
    FileOperator operator;
    public MyInvocationHandler(FileOperator operator) {
            this.operator = operator;
    }
   @Override
       public void invoke(Object proxy, Method method, Object[] args) {
            if (method.getName().equals("writeToFile")) {
                    //print log
                    operator.writeToFile();
            } else {
                  method.invoke(operator, args);
            }
       }
     
    public IFileOperator createProxy(IFileOperator operator) {
        IFileOperator proxy = (IFileOperator)       
        Proxy.newInstance(ClassLoader.getSystemClassLoader(), new 
        Class[] {IFileOperator.class}, new MyInvocationHandler(operator);
        return proxy;
}

可以看到,只需要对特定的方法做出来就行了,处于方法不做任何处理,就可以暴露原始对象的所有方法,避免了大量无用的重复方法, 这就是动态代理的优势。这里可能会有一个担心:每次创建代理的时候都需要动态创建一个Proxy Class,会不会有很多开销?实际上是不用担心的,Proxy.newInstance方法首先根据传入的interface列表创建Class,创建前会先检查对应的列表是否已经创建过Proxyclass,如果已创建,则复用,这样实际上每次调用Proxy.newInstance也只是创建了一个Proxy对象而已,开销跟静态代理是一样的

    原文作者:nightkidjj
    原文地址: https://www.jianshu.com/p/30694acffe61
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞