美文网首页
动态代理

动态代理

作者: nightkidjj | 来源:发表于2017-04-30 01:12 被阅读11次

    代理模式

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

    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对象而已,开销跟静态代理是一样的

    相关文章

      网友评论

          本文标题:动态代理

          本文链接:https://www.haomeiwen.com/subject/dguttxtx.html