谈谈设计模式之代理模式及关于动态代理的实现原理

   代理模式式是常用的结构型设计模式之一,当无法直接获取到某个模块中某个类中的代码进行修改时,或像在一些好的模块中动态的添加一些其他功能时。则可以使用到代理模式。

代理模式又细分为动态代理、静态代理、远程代理等等。
举个生活中的例子进行类比,代理模式类似与现在流行的代购,客户想获取懂国外的某个品牌产品,但由于种种原因无法去直接获取到该产品,这时候代购就出现了,代理就像一个中间人,客户只需要跟代理进行沟通,到代理处获取这个产品,进而让代理再去完成那个跟那个品牌商拿货的动作,代理是不会生产那个品牌的产品的,代购也是依赖与品牌商那拿货。而在拿到货之前或之后,代理可以做些其他的业务操作,比如在拿到产品返回用户之前,收取用户一点代购费之类的。其实代理就是这么简单。商品代购过程如下图所示:

代理模式定义如下:

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。 Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it.

    貌似官方的定义总是那么拗口,用我的话来理解就是,在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。

代理模式包含如下三个角色:
(1) Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
(2) Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
(3) RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
下面来看代码:

interface Subject  
{  
   void request();  
}

class RealSubject implements Subject  
{  
   public override void request()  
   {  
       //业务方法具体实现代码  
   }  
}

class Proxy implements Subject  
{  
   //维持一个对真实主题对象的引用  
   private RealSubject realSubject = new RealSubject(); 

   public void PreRequest()   
   {  
       …...  
   }  

   @override
   public  void request()   
   {  
         PreRequest();  
         realSubject.Request(); //调用真实主题对象的方法  
         PostRequest();  
   }  

   public void PostRequest()   
   {  
       ……  
   }  
}
    还是说上面代购的例子,当不同的用户需要不同国家的不同商品时,如果采用静态代理的方法,将会要写很多这样的类似代码,代码冗余度增加,而且不便于拓展。 还有就是静态代理,代理类的生成需要一个确切的原始类,这在实际很多场景中都有局限性。这时动态代理就弥补了这些不足。
  动态代理中所谓的“动态”,是针对使用java代码实际编写了代理类的“静态”代理而言的,它的优势不在于省去了编写代理类那一点工作量,更多的是实现了可以在原始类和接口还未知的时候,就确定代理类的代理行为,当代理类和原始类脱离直接联系后,就可以很灵活地重用于不同的应用场景之中。
     对于很多框架而言,动态代理所带来的特性,尤为重要。比如Spring中AOP的实现,像这种框架在编写时,是不知道所需要代理的类是什么样的,如果采用静态代理的方式,将无法完成原始类的代理工作。

还是来通过代码来感受下,java提供的动态代理,是怎么在未知原始类和接口的情况下,完成代理工作的。
动态代理简单示例:

package com.javaSE.myproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
interface IHello{
    void sayHello();
}
static class Hello implements IHello{
  @Override
  public void sayHello() {
    System.out.println("Hello word!!!");
  }
}
//动态代理实现了在原始类和接口还未知的情况下就能先确定代理类的代理行为。这样就可以很灵活的运用在不同的场景中
static class DynamicProxy implements InvocationHandler{
    T originalObj;
    Object bind(T originalObj){
        this.originalObj = originalObj;
        return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(),this);
    }
/*
* 调用被代理对象中的任何方法都会执行该方法
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("welcome!!!");
        return method.invoke(originalObj, args);
    }
}
  public static void main(String[] args) {
      //如果想看一看这个运行时产生的代理类中写了些什么,在以在执行时加入如下一行代码     
System.getProperties().put("sum.misc.ProxyGenerator.saveGeneratedFiles", "true");
     IHello proxy =(IHello)new DynamicProxy().bind(new Hello());
     proxy.sayHello();
  }
}

在这里还有几个小问题,为什么生成的代理类,执行该类的任意方法都是调用InvocationHandler 中的invoke方法呢?java虚拟机给我们生成的代理类到底是什么样子的呢?我们通过在执行时加入如下一行代码,找到JVM所生成的代理类。
System.getProperties().put("sum.misc.ProxyGenerator.saveGeneratedFiles", "true");

反编译$Proxy0.class动态代理类的代码

package com.javaSE.myproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements DynamicProxyTest.IHello {
  private static Method m1;
  private static Method m0;
  private static Method m3;
  private static Method m2;

  static {
      try {
          m1 = Class.forName("java.lang.Object").getMethod("equals",
                  new Class[] { Class.forName("java.lang.Object") });
          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
          m3 = Class.forName("com.javaSE.myproxy.DynamicProxyTest$IHello").getMethod("sayHello", new Class[0]);
          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      } catch (NoSuchMethodException nosuchmethodexception) {
          throw new NoSuchMethodError(nosuchmethodexception.getMessage());
      } catch (ClassNotFoundException classnotfoundexception) {
          throw new NoClassDefFoundError(classnotfoundexception.getMessage());
      }
  }

  public $Proxy0(InvocationHandler invocationhandler) {
      super(invocationhandler);
  }

  @Override
  public final boolean equals(Object obj) {
      try {
          return ((Boolean) this.h.invoke(this, m1, new Object[] { obj })).booleanValue();
      } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
      }
  }

  @Override
  public final int hashCode() {
      try {
          return ((Integer) this.h.invoke(this, m0, null)).intValue();
      } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
      }
  }

  public final void sayHello() {
      try {
          this.h.invoke(this, m3, null);
          return;
      } catch (Error e) {
      } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
      }
  }

  @Override
  public final String toString() {
      try {
          return (String) this.h.invoke(this, m2, null);
      } catch (Throwable throwable) {
          throw new UndeclaredThrowableException(throwable);
      }
  }
}
     这个代理类的实现代码也很简单,它为传入接口中的每一个方法,以及继承至Object中继承来的equals(),hashCode(),toString()方法都生成了对应的实现,并且统一调用了InvocationHandler对象的invoke( )方法(代码中的"this.h",就是父类Proxy中保存的InvocationHandler 实例变量)来实现这些方法的内容,各个方法的区别不过是传入的参数和Method对象有所不同而已,所以无论调用动态代理类的哪一个方法,实际上都是在执行InvocationHandler.invoke()中的代理逻辑。
  <h6>至此,关于代理模式,动态代理的使用及原理都讲完了,是不是感觉并没有啥难的~</h6>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容