设计模式 -- 代理模式 (Proxy Pattern)


代理模式也叫委托模式
定义:为其他对象提供一种代理以控制对这个对象的访问。

代理模式通用的类图如下:

代理模式 .png

我们来分析类图中的几个角色定义:
①Subject 抽象主题角色:可以是抽象类,也可以是接口,是一个最普通的业务类型定义。
②RealSubject 真实主题角色:也叫做被委托角色,被代理角色,是Subject 抽象主题角色的具体实现者,业务逻辑具体执行者
③Proxy 代理主题角色:也叫做委托类,代理类。负责对真实角色的应用,把所有抽象主题委托的方法限制给真实主题角色实现,并且在真实主题角色处理完毕前后做一些预处理和善后工作。

通用代码实现
Subject 抽象主题角色

public interface Subject {
   public void request();
}

RealSubject 真实主题角色

public class RealSubject implements Subject {
   @Override
   public void request() {
      //业务处理
   }
}

Proxy 代理主题角色:

public class Proxy implements Subject {
   private Subject subject = null;
   
   public Proxy(Subject subject) { //通过构造函数指定传递被代理者
      this.subject = subject;
   }
   
   @Override
   public void request() {
      this.Before();
      this.subject.request();
      this.After();
   }

  //预处理
   private void Before(){
      //代理前业务处理
   }

    //善后处理
   private void After(){
      //代理后业务处理
   }
}

一个代理类可以代理多个被委托者,因此我们需要在高层模块中自己指定代理类具体代理哪个真实主题角色,也就是代码中所写的,通过构造函数传递被代理者。

代理模式的优点
①职责清晰:真实的角色就是实现实际的业务逻辑,不用关系其他非本职责的事物。
②高扩展性:真实主题角色是随时变化的,不管它怎么变,只要是实现了接口,都能被在不修改代理类下使用
③智能化:动态代理

场景模拟
使用代理模式实现代练公司给玩家代练游戏账号
玩家接口(抽象主题角色):定义登录,杀怪,升级 三种行为

public interface IGamePlayer {
   //登录账户名与密码
   public void login(String username,String password);
   //杀怪
   public void killBoss();
   //升级
   public void upgrade();
}

玩家实现类(真实主题角色)

public class GamePlayer implements IGamePlayer{
   private String name = "";
   
   public GamePlayer(String name) {
      this.name = name;
   }
   
   @Override
   public void login(String username, String password) {
      System.out.println("登录名为:"+username+" 密码为:"+password+" 登录成功");
   }
   
   @Override
   public void killBoss() {
      System.out.println(this.name+" 在打boss");
   }
   
   @Override
   public void upgrade() {
      System.out.println(this.name+ " 升级了!");
   }
}

代理类(代理主题角色类)

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通过构造函数传递对谁进行代练
   public GamePlayerProxy(IGamePlayer gamePlayer) {
      this.gamePlayer = gamePlayer;
   }
   
   @Override
   public void login(String username, String password) {
      this.gamePlayer.login(username,password);
   }
   
   @Override
   public void killBoss() {
      this.gamePlayer.killBoss();
   }
   
   @Override
   public void upgrade() {
      this.gamePlayer.upgrade();
   }
}

客户端

public class Client {
   public static void main(String[] args) {
      System.out.println("---------未使用代理模式----------");
      IGamePlayer player = new GamePlayer("张三");
      player.login("zhangsan","123456");
      player.killBoss();
      player.upgrade();
      
      System.out.println("---------代理模式----------");
      GamePlayerProxy gamePlayerProxy = new GamePlayerProxy(player);
      gamePlayerProxy.login("张三","123456");
      gamePlayerProxy.killBoss();
      gamePlayerProxy.upgrade();
   }
}

代理模式的扩展:
类似于网络代理服务器设置,代理模式也分为:普通代理与强制代理
(1)普通代理:用户必须知道代理的存在,通过代理寻找真实角色,也就是Proxy 代理主题角色这个类的存在是对用户透明的。普通代理的要求是客户端只能访问代理角色,而不能访问RealSubject 真实主题角色。那么使用普通代理改造上述场景模拟,代理类与玩家实现类修改如下
玩家实现类

public class GamePlayer implements IGamePlayer{
   private String name = "";
   //通过构造函数确认谁能创建真实对象
   public GamePlayer(IGamePlayer gamePlayer,String name) {
      if (gamePlayer == null){
         throw new RuntimeException("不能创建真实角色");
      }else {
         this.name = name;
      }
   }
   /.....以下省略...../

代理类

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通过构造函数传递对谁进行代练
   public GamePlayerProxy(String username) {
      try {
         //将代理类传递过去是为了检查谁能创建真实角色
         this.gamePlayer = new GamePlayer(this,username);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /.....以下省略...../

客户端

public class Client {
   public static void main(String[] args) {
      System.out.println("---------使用普通代理模式----------");
      IGamePlayer player = new GamePlayerProxy("张三");
      player.login("zhangsan","123456");
      player.killBoss();
      player.upgrade();
   }
}

经过如上代码修改,我们不需要知道真实角色是谁,屏蔽真实角色对高层模块的影响,扩展性强。

(2)强制代理:与普通代理相反,强制代理是通过真实角色寻找代理,否则无法访问(无论是通过代理还是直接new一个主题类对象)。那么使用强制代理改造上述场景模拟,代码修改如下

既然是通过真实角色获取代理对象,那么玩家类就需要修改
玩家接口修改

public interface IGamePlayer {
   //登录账户名与密码
   public void login(String username,String password);
   //杀怪
   public void killBoss();
   //升级
   public void upgrade();
   //获取代理类
   public IGamePlayer getProxy();
}

玩家实现类修改

public class GamePlayer implements IGamePlayer{
   private String name = "";
   private IGamePlayer proxy = null;
   //通过构造函数确认谁能创建真实对象
   public GamePlayer(String name) {
      this.name = name;
   }
   
   @Override
   public void login(String username, String password) {
      if (this.isProxy()){
         System.out.println("登录名为:"+username+" 密码为:"+password+" 登录成功");
      }else {
         System.out.println("请使用指定的代理访问");
      }
   }
   
   @Override
   public void killBoss() {
      if(this.isProxy()){
         System.out.println(this.name+" 在打boss");
      }else {
         System.out.println("请使用指定的代理访问");
      }
   }
   
   @Override
   public void upgrade() {
      if (this.isProxy()){
         System.out.println(this.name+ " 升级了!");
      }else {
         System.out.println("请使用指定的代理访问");
      }
   }
   
   @Override
   public IGamePlayer getProxy() {
      //指定代理对像
      this.proxy = new GamePlayerProxy(this);
      return this.proxy;
   }
   
   //检测是否是代理访问
   private boolean isProxy(){
      if (this.proxy == null){
         return false;
      }else {
         return true;
      }
   }
}

代理类修改

public class GamePlayerProxy implements IGamePlayer {
   private IGamePlayer gamePlayer = null;
   //通过构造函数传递对谁进行代练
   public GamePlayerProxy(IGamePlayer gamePlayer) {
      this.gamePlayer = gamePlayer;
   }
   
   @Override
   public void login(String username, String password) {
      this.gamePlayer.login(username,password);
   }
   
   @Override
   public void killBoss() {
      this.gamePlayer.killBoss();
   }
   
   @Override
   public void upgrade() {
      this.gamePlayer.upgrade();
   }
   
   @Override
   public IGamePlayer getProxy() {
      //代理的代理没有,默认返回自己
      return this;
   }
}

客户端:

public class Client {
   public static void main(String[] args) {
      System.out.println("---------直接访问真实角色----------");
      IGamePlayer player1 = new GamePlayer("张三");
      player1.login("zhangsan","123456");
      player1.killBoss();
      player1.upgrade();
      
      System.out.println("---------直接访问真实角色----------");
      IGamePlayer player2 = new GamePlayer("张三");
      IGamePlayer proxy = new GamePlayerProxy(player2);
      proxy.login("zhangsan","123456");
      proxy.killBoss();
      proxy.upgrade();
      
      System.out.println("---------使用强制代理模式----------");
      IGamePlayer player3 = new GamePlayer("张三");
      IGamePlayer proxy3 = player3.getProxy();
      proxy3.login("zhangsan","123456");
      proxy3.killBoss();
      proxy3.upgrade();
   }
}
-------------output--------------
---------直接访问真实角色----------
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
---------直接访问真实角色----------
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
---------使用强制代理模式----------
登录名为:zhangsan 密码为:123456 登录成功
张三 在打boss
张三 升级了!

经过上面代码的演示,可以看出强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。 在客户端中使用getProxy方法就可以取得真实角色的代理,然后访问真是角色的所有方法。

动态代理
定义:在实现阶段不需要知道代理谁,在运行阶段才指定代理对象。
使用JDK提供的动态代理接口:java.lang.reflect.InvocationHandler,对被代理类的方法进行处理。
具体代码如下:

public class GamePlayIH implements InvocationHandler {
   //被代理者类
   Class cls = null;
   //被代理的实例对象
   Object obj = null;
   //通过构造函数传递被代理的实例对象
   public GamePlayIH(Object obj) {
      this.obj = obj;
   }
   //调用被代理的方法,invoke方法必须重写,它完成对真实方法的调用
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = method.invoke(this.obj,args);
      return result;
   }
}

动态代理是根据被代理的接口获取所有的方法,也就是给定一个接口,动态代理就会自动实现该接口下的所有方法,默认情况下所有方法返回值都为空值,也就是虽然动态代理虽然实现接口下的所有方法,但是并没有包含任何逻辑,因此还需要通过InvocationHandler接口来处理所有方法的实际任务。

客户端

public class Client {
   public static void main(String[] args) {
      //定义一个玩家
      IGamePlayer player = new GamePlayer("张三");
      //定义一个handler
      InvocationHandler handler = new GamePlayIH(player);
      //获取类加载器
      ClassLoader classLoader = player.getClass().getClassLoader();
      //在这里给定IGamePlayer接口与InvocationHandler接口,通过Proxy动态产生一个代理者
      IGamePlayer gamePlayerProxy = (IGamePlayer) Proxy.newProxyInstance(classLoader,new Class[]{IGamePlayer.class},handler);
      gamePlayerProxy.login("zhangsan","123456");
      gamePlayerProxy.killBoss();
      gamePlayerProxy.upgrade();
   }
}

让我们来看看动态代理的模型:动态代理通用类图

动态代理通用类图.png

根据通用类图,我们来写出通用的模式代码

public interface Subject {
   public void doSomething(String str);
}

public class RealSubject implements Subject {
   @Override
   public void doSomething(String str) {
      System.out.println("doSomething ---> "+str);
   }
}

public class MyInvocationHandler implements InvocationHandler {
   //被代理的实例对象
   Object obj = null;
   //通过构造函数传递被代理的实例对象
   public MyInvocationHandler(Object obj) {
      this.obj = obj;
   }
   //调用被代理的方法
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = method.invoke(this.obj,args);
      return result;
   }
}

public interface IAdvice {
   //通知只有一个执行方法
   public void exec();
}

public class BeforeAdvice implements IAdvice {
   @Override
   public void exec() {
      System.out.println("前置通知执行!");
   }
}

public class DynamicProxy<T> {
   public static <T> T newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h){
      //寻找JoinPoint连接点,AOP框架使用元数据定义
      if (true){
         //执行一个前置通知
         new BeforeAdvice().exec();
      }
      //执行目标,并返回结果
      return (T)Proxy.newProxyInstance(loader,interfaces,h);
   }
}

客户端

public class Client {
   public static void main(String[] args) {
      //定义一个真实主题
      Subject subject = new RealSubject();
      //定义一个代理handler
      InvocationHandler handler = new MyInvocationHandler(subject);
      //定义主题的代理
      Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);
      //执行代理的行为
      proxy.doSomething("zhangsan");
   }
}

在这里注意看:

DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),handler);

这段代码。subject.getClass().getClassLoader():获取subject对象的类加载器,subject.getClass().getInterfaces()是获取Subject类的所有接口,最后指定代理handler后,会实现该类的所有方法,由MyInvocationHandler类中的invoke方法接管所有的方法实现。

因此proxy.doSomething("zhangsan")这段代码的调用过程为:Client类 --> DynamicProxy代理类 --> InvocationHandler接口 --> MyInvocationHandler 实现类 --> 接管Subject接口的所有方法 --> RealSubject实现类

动态代理与静态代理的区别:动态代理是面向切面编程,在不改变我们已有的代码结构下增强或控制对象的行为。

注意:实现动态代理的首要条件是:被代理类必须实现一个接口,当然在CGLIB等技术上不需要接口也可以实现。

参考书籍:设计模式之禅 --- 秦小波 著

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容