2006-12-27

Proxy Pattern Story

关键字: proxy

     说到“代理”也许大家一定不会陌生,每天我们都要去找“代理”才能上外网,每天我们都会看到广告栏上贴着“急聘××代理”,我们配电脑时,每一个散件也是在各个品牌的代理商手里买到的...可以说我们的生活和“代理”是密切相关的,我们通过代理商得到了自己想要的东西,而且还享受到了代理商额外的服务;而生产厂商通过代理商将自己的产品推广出去,而且可以将一些销售服务的任务交给代理商来完成(当然代理商要和厂商来共同分担风险,分配利润),这样自己就可以花更多的心思在产品的设计和生产上了。
     而这些和程序设计有什么关系呢?难道在程序设计也存在“代理”角色吗?如果有的话,它的作用是什么呢?
     首先让我们从结构上看一看什么是Proxy Pattern,代理模式中的“代理商”要想实现代理任务,就必须和被代理的“厂商”使用共同的接口(当然通过继承也可以实现的)。于是代理模式就有三个角色组成了:
      ----- 抽象主题角色:声明了真实主题和代理主题的共同接口。
      ----- 代理主题角色:内部包含对真实主题的引用(通过组合实现),并且提供和真实主题角色相同的接口。
      ----- 真实主题角色:定义真实的对象。
     下面是具体的示例代码:
     抽象主题角色接口:  
     客户端引用代理的代码:

  1. public interface Subject {   
  2.      public void request();   
  3. }   

     真实主题角色类:

  1. public class RealSubject implements Subject {   
  2.      public RealSubject() {    
  3.     
  4.      }   
  5.      public void request() {    
  6.          do some thing you want...   
  7.      }   
  8. }   

     代理主题角色类:

  1. public class ProxySubject implements Subject {   
  2.      private RealSubject realSubject;       
  3.      public ProxySubject(RealSubject realSubject) {   
  4.          this.realSubject = realSubject;   
  5.      }   
  6.      public void request() {   
  7.          preRequest();     
  8.          realSubject.request();     
  9.          postRequest();    
  10.      }   
  11.      private void preRequest() {   
  12.          do some thing you want to do before requesting...   
  13.      }   
  14.      private void postRequest() {   
  15.          do some thing you want to do after requesting...   
  16.      }   
  17. }   

     客户端如何利用代理的代码:

  1. Subject proxy=new ProxySubject();   
  2. proxy.request();   

     了解了结构,现在让我们深入了解一下代理模式的内涵。
     代理模式的目的:为其他对象提供一种代理以控制对这个对象的访问。在一些情况下客户不想或者不能直接引用一个对象,而代理对象可以在客户和目标对象之间起到中介作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。
     现在是问题的关键:什么时候要使用代理模式呢?
     首先我们假设出现了如下的情形:在对已有的方法进行使用的时候出现需要对原有方法进行改进或者修改,这时候有两种改进选择:修改原有方法来适应现在的使用方式,或者使用一个“中间层”方法来调用原有的方法并且对方法产生的结果进行一定的控制。第一种方法是明显违背了“对扩展开放、对修改关闭”(开闭原则),而且在原来方法中作修改可能使得原来类的功能变得模糊,而使用第二种方式可以将功能划分的更加清晰,一定程度之上降低了耦合,有助于后面的维护。所以在一定程度上第二种方式是一个比较好的选择!下面是几个应用代理模式典型的情形:
      ----- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。比如:你可以将一个在世界某个角落一台机器通过代理假象成你局域网中的一部分。比如RMI中的stub和skeleton。
      ----- 虚拟(Virtual)代理:根据需要将一个资源消耗很大或者比较复杂的对象延迟的真正需要时才创建。比如:如果一个很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,这个大图片可能就影响了文档的阅读,这时需要做个图片Proxy来代替真正的图片。总之原则是,对于开销很大的对象,只有在使用它时才创建,这个原则可以为我们节省很多宝贵的Java内存。
      ----- 保护(Protect or Access)代理:控制对一个对象的访问权限。比如:在论坛中,不同的身份登陆,拥有的权限是不同的,使用代理模式可以控制权限,下面是一个简单的实例:

  1. public interface MyForum {   
  2.        public void AddFile();   
  3.    }   
  4.    public interface Constants {   
  5.        public static final int ASSOCIATOR = 1;    
  6.        ......   
  7.    }   
  8.    public class RealMyForum implements MyForum {   
  9.        public void AddFile() {   
  10.            ......   
  11.        }   
  12.    }   
  13.    public class MyForumProxy implements MyForum {   
  14.        private RealMyForum forum ;   
  15.        private int permission ;       
  16.        public MyForumProxy(MyForum forum, int permission) {   
  17.            this.forum = forum;    
  18.            this.permission = permission ;   
  19.        }   
  20.        public void AddFile() {   
  21.            if(Constants.ASSOCIATOR == permission) {   
  22.                forum.AddFile();   
  23.            } else    
  24.                .......   
  25.            }   
  26.    }   

     也许你会疑问为什么不直接把判断权限的if-else写在RealMyForum里面呢,那样的话就不需要去增加代理类了,但是如果这样作的话,无形之中增加了系统的耦合,AddFile除了完成自己的责任,还要完成权限控制的责任,使得AddFile的业务不再保持纯洁,降低了其可复用性,对于以后的维护带来了很大的难度。所以我们需要将权限控制的责任提取出来,交给MyForumProxy类来完成这些额外的工作。
      ----- 智能引用(Smart Reference)代理:提供比对目标对象额外的服务。比如:纪录访问的流量等等。
      ----- Copy-On-Write  简单来说,Copy-On-Write是在复制一个对象时并不是真的在内存中把原来对象的数据复制一份到另外一个地址,而是在新对象的内存映射表中指向同原对象相同的位置,并且把那块内存的Copy-On-Write位设为1。在对这个对象执行读操作的时候,内存数据没有变动,直接执行就可以。在写的时候,才真正将原始对象复制一份到新的地址,修改新对象的内存映射表到这个新的位置,然后往这里写。拷贝一个庞大而复杂的对象是一个开销很大的操作,如果拷贝过程中,没有对原来的对象有所修改,那么这样的拷贝开销就没有必要。Proxy可以对用户隐藏copy-on-write 的优化方式,利用代理模式可以延迟这一拷贝过程。
     代理模式是设计模式里比较明星的模式了,从像上面例子的几个类的“小结构”到像流行的分布计算方式RMI和CORBA这样的“大结构”都可以看到它的身影。
     不知道我们是否想到,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题引出了Java了一个很有趣的机制--动态代理。
     所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
     Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:
      ----- Interface InvocationHandler:该接口中仅定义了一个方法Object invoke(Object obj,Method method, Object[] args)。在实际使用时第一个参数obj一般是指代理类,method是被代理的方法,如开篇时的例子中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。
      ----- Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:
         Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。
         Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
        Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。
     现在让我们利用Dynamic Proxy改造开篇时的那个例子,抽象主题角色接口和真实主题角色类不需要改动
     代理主题角色类代码:

  1. public class ProxySubject implements InvocationHandler {   
  2.      private Object sub;   
  3.      public ProxySubject() {   
  4.   
  5.      }   
  6.      public ProxySubject(Object obj) {   
  7.         sub = obj;   
  8.      }   
  9.      public Object invoke(Object proxy, Method method, Object[] args)   
  10.            throws Throwable {   
  11.         preRequest();     
  12.         method.invoke(sub,args);   
  13.         postRequest();    
  14.      }   
  15.      private void preRequest() {   
  16.         do some thing you want to do before requesting...   
  17.      }   
  18.      private void postRequest() {   
  19.         do some thing you want to do after requesting...   
  20.      }   
  21. }   

      客户端引用代理的代码:

  1. public class Client {   
  2.       static public void main(String[] args) throws Throwable {   
  3.            RealSubject rs = new RealSubject();     
  4.            InvocationHandler ds = new DynamicSubject(rs);   
  5.            Class cls = rs.getClass();   
  6.            Subject subject = (Subject)Proxy.newProxyInstance(   
  7.                   cls.getClassLoader(),cls.getInterfaces(),ds);   
  8.            subject.request();   
  9. }   

     你是否隐约地感到了Dynamic Proxy的强大了呢,对于动态代理的详细介绍以及其对程序设计带来的影响,透明的那篇《动态代理的前世今生》可谓是经典之作了,大家有兴趣的话,可以仔细的看一看。

评论
lkjust08 2008-07-31   回复
二楼的说的很有道理,楼主可否作个解释
yuyang030405 2008-04-14   回复
public class Client {    
      static public void main(String[] args) throws Throwable {    
           RealSubject rs = new RealSubject();//这是个真实对象,是已经存在于客户端程序里面的新 new 的一个对象。      
           InvocationHandler ds = new DynamicSubject(rs); //此处的DynamicSubject 是指你上一文档中的 ProxySubject 类吗?  
           Class cls = rs.getClass();    
           Subject subject = (Subject)Proxy.newProxyInstance(    
                  cls.getClassLoader(),cls.getInterfaces(),ds); //Subject 接口已经被 RealSubject 类实现
           subject.request();  //接口调用,实际是调用实现类的方法
            //问题就是:绕了一个大圈子,我还是要在客户端程序中 new 一个接口实现类的对象,这和 Subject subject = new RealSubject();有什么区别吗?
//也许可以这样: Subject subject = SubjectFactory.getInstance();但这又不是我们讨论的重点。
//可能是我对 proxy 的理解还有偏差,所以没有体会出来客户端代码在应用代理之后的直接效果。
//如果您有时间和精力,麻烦帮个忙给我解决下我的困惑,谢谢!
      }
}
yuyang030405 2008-04-14   回复
public class Client {    
      static public void main(String[] args) throws Throwable {    
           RealSubject rs = new RealSubject();//这是个真实对象,是已经存在于客户端程序里面的新 new 的一个对象。      
           InvocationHandler ds = new DynamicSubject(rs); //此处的DynamicSubject 是指你上一文档中的 ProxySubject 类吗?  
           Class cls = rs.getClass();    
           Subject subject = (Subject)Proxy.newProxyInstance(    
                  cls.getClassLoader(),cls.getInterfaces(),ds); //Subject 接口已经被 RealSubject 类实现
           subject.request();  //接口调用,实际是调用实现类的方法
            //问题就是:绕了一个大圈子,我还是要在客户端程序中 new 一个接口实现类的对象,这和 Subject subject = new RealSubject();有什么区别吗?
//也许可以这样: Subject subject = SubjectFactory.getInstance();但这又不是我们讨论的重点。
//可能是我对 proxy 的理解还有偏差,所以没有体会出来客户端代码在应用代理之后的直接效果。
//如果您有时间和精力,麻烦帮个忙给我解决下我的困惑,谢谢!
      }
}
spark_zeng 2007-08-16   回复
你好:
看到你的文章很受启发;
但是我还有一些疑虑,特别是dynamic proxy
它相对于静态代理有什么优势
动态体现在什么地方;
我觉的所体现出的动态性体现在

    public class ProxySubject implements InvocationHandler {   
         private Object sub;  //RealSubject可以是任何对象 
        public ProxySubject() {   
     
        }   
         public ProxySubject(Object obj) {   
            sub = obj;   
         }   
         public Object invoke(Object proxy, Method method, Object[] args)   
             throws Throwable {   
          preRequest();     
           method.invoke(sub,args);  //在这句话体现那动态 
           postRequest();    
        }   
        private void preRequest() {   
           do some thing you want to do before requesting...   
        }   
        private void postRequest() {   
           do some thing you want to do after requesting...   
      }   
   }   


youthon 2007-01-25   回复
不错
sjx003123 2007-01-09   回复
发表评论

您还没有登录,请登录后发表评论

aladdin_leon
搜索本博客
最近加入圈子
存档
最新评论
评论排行榜