真刀实枪之代理模式
-
我是游戏至尊
- “最近几年王者荣耀的热度飙升,自己打时可以体验到其中的升级乐趣,但是时间过得很快啊!自己不想打,找代练,好主意!”
- 作为一名程序员,先将打游戏这段过程系统化一下
-
代码
-
IGamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public interface IGamePlayer { // 登录系统 public void login(String username, String password); // 杀怪 public void killBoss(); // 升级 public void upgrade(); }
-
GamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayer implements IGamePlayer { private String username; private String password; public GamePlayer(String username, String password) { super(); this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public void login(String username, String password) { System.out.println(username + "登录了!"); } @Override public void killBoss() { System.out.println("在打怪!"); } @Override public void upgrade() { System.out.println("又升一级!"); } }
-
Client
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) { // 创建游戏玩家 IGamePlayer igp = new GamePlayer("123", "***"); // 登录游戏系统 igp.login("123", "***"); // 杀野怪 igp.killBoss(); // 升级 igp.upgrade(); } }
-
- 心里学家告诉我们:人类对于困难的记忆比对喜悦的记忆要深刻,但是人类对于喜悦是“趋利”性的,每个人都是想Happy的,都不想让苦难靠近,要都不想让苦难靠近,要想获得幸福,苦难也是在所难免的。
-
游戏玩的时间长了,腰酸背痛眼睛干涩,手臂酸麻。其结果就像吃了“一日丧命散”,静脉逆流,胡思乱想,而走火入魔,那该怎们解决呢,我们也想玩游戏,但又不想碰触到游戏的烦恼。如何解决,代练公司这么多,哈哈,这么好,先修改下类图。
-
代码
-
GamePlayerProxy
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ 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(); } }
-
Client
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) { // 创建游戏玩家 GamePlayerProxy gpp = new GamePlayerProxy(new GamePlayer("123", "***")); // 登录游戏系统 gpp.login("123", "***"); // 杀野怪 gpp.killBoss(); // 升级 gpp.upgrade(); } }
-
-
- 现在有人帮你打游戏了,终于升到30级,至尊星耀,啊哈哈,现在来看看具体的代理模式吧!
代理模式的定义
- Proxy Pattern
- Provide a surrogate or placeholder for another object to control access to it.(为了其他对象提供一种代理以控制对这个对象的访问!)
- 通用类图
- 代理模式也叫委托模式,它是一项基本的设计技巧,许多其他的模式,如状态模式、策略模式、访问者模式在本质上是在你更特殊的场合采取了委托模式,而且在日常的应用中,代理模式可以提供更好的访问控制。(Struts2的Form元素映射就是采用了代理模式:动态代理模式)
- 类图角色
- Subject抽象主题角色:可以是接口或者是抽象类
- RealSubject具体主题角色:也被叫做委托角色、被代理角色。他才是冤大头,是业务逻辑的具体执行者
- Proxy代理主题角色:也叫做委托类、代理类,他负责对真实角色的应用,把所有的抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕后做预处理和善后处理。
-
代码
-
抽象主题类
public interface Subject { // 定义一个方法 public void request(); }
-
真实主题类
public class RealSubject implements Subject { @Override public void request() { // 业务逻辑处理 } }
-
代理类
public class Proxy implements Subject { // 要代理哪个实现类 private Subject subject = null; // 默认被代理者 public Proxy() { this.subject = new Proxy(); } // 传递被代理者 public Proxy(Object... object) { //这里自己悟一下--简单提示,自己拓展 //public Proxy(RealSubject subject) { // this.subject=subject; //} } @Override public void request() { this.before(); this.request(); this.after(); } private void before() { // 预处理操作 } private void after() { // 善后处理 } }
- 看到这里先别惊讶为啥还有before方法和after方法。
- 一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代表哪个真实主题角色是由场景类来决定。当然,最简单的代理模式是一个主题类和一个代理类。通常情况下,一个接口只需要一个代理类就可以了,具体代理哪个实现类由高层模块来决定,也就是在代理类的构造函数中传递被代理者。
-
代理模式的应用
- 代理模式的优点
- 职责清晰
- 高扩展性
- 智能化(提前了解一下:动态代理)
- 代理模式应用场景
- 现实世界中出现了一种律师:你不想参与是是非非,只要完成自己的答辩就可以,比如事前调查,事后追查不用你管,律师就可以做了。Spring AOP是一个非常典型的动态代理。
代理模式的拓展
- 网络上代理服务器分为透明代理和普通代理,那是什么意思呢?
- 透明代理服务器:用户不用设置代理服务器地址,就可以直接访问,也就是服务器对用户来说是透明的,不用知道它的存在
- 普通代理服务器:需要用户自己设置服务器的IP地址,用户必须知道代理的存在
- 强制代理:调用者直接调用真实角色,而不用关心代理是真实存在,其代理的产生是由真实角色决定的
-
看下不同代理模式的类图吧
-
普通代理
-
改动很小,仅仅是修改了两个实现类的构造函数,GamePlayer增加了一个_gamePlayer的参数,而代理模式则传入代理者的名称即可
-
GamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayer implements IGamePlayer { private String name=""; //_gamePlayer放代理者 public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception { super(); if(_gamePlayer==null){ throw new Exception("不能创建真实角色!"); }else{ this.name=_name; } } @Override public void login(String username, String password) { System.out.println(username + "登录了!"); } @Override public void killBoss() { System.out.println(this.name+"在打怪!"); } @Override public void upgrade() { System.out.println(this.name+"又升一级!"); } }
-
GamePlayerProxy
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayerProxy implements IGamePlayer { private IGamePlayer gamePlayer = null; // 通过构造函数对谁进行代练 public GamePlayerProxy(String name) { try{ gamePlayer=new GamePlayer(this,name); }catch(){ //异常处理 } } // 代练登录 @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(); } }
-
场景类
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) { // 创建游戏玩家 GamePlayerProxy gpp = new GamePlayerProxy(new GamePlayer("kungfu~peng")); // 登录游戏系统 gpp.login("123", "***"); // 杀野怪 gpp.killBoss(); // 升级 gpp.upgrade(); } }
-
在该模式下,调用者只知道代理而不用真实的角色是谁,屏蔽了真实角色的变更对高层对象的影响
-
-
-
强制代理
- 通过真实角色找到代理,只有通过真实角色指定的代理才可以用。即创建真实角色的时候却创建了个代理出来。
-
-
IGamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public interface IGamePlayer { // 登录系统 public void login(String username, String password); // 杀怪 public void killBoss(); // 升级 public void upgrade(); // 返回代理 public IGamePlayer getProxy(); }
-
GamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayer implements IGamePlayer { private String name = ""; private IGamePlayer proxy = null; public GamePlayer(String _name) throws Exception { super(); this.name = _name; } @Override public void login(String username, String password) { if (isProxy()) { System.out.println(username + "登录了!"); } else { System.out.println("请使用指定的代理访问!"); } } @Override public void killBoss() { if (isProxy()) { System.out.println(this.name + "在打怪!"); } else { System.out.println("请使用指定的代理访问!"); } } @Override public void upgrade() { if (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; } } }
-
GamePlayerProxy
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ 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; } }
-
Client(测试1)
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) throws Exception { // 创建游戏玩家 IGamePlayer gpp =new GamePlayer("kungfu~peng"); // 登录游戏系统 gpp.login("123", "***"); // 杀野怪 gpp.killBoss(); // 升级 gpp.upgrade(); } } //结果: 请使用指定的代理访问! 请使用指定的代理访问! 请使用指定的代理访问!
-
Client(测试2)
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) throws Exception { // 创建游戏玩家 IGamePlayer gpp =new GamePlayer("kungfu~peng"); //代练者 IGamePlayer proxy =new GamePlayerProxy(gpp); // 登录游戏系统 proxy.login("123", "***"); // 杀野怪 proxy.killBoss(); // 升级 proxy.upgrade(); } } //结果: 请使用指定的代理访问! 请使用指定的代理访问! 请使用指定的代理访问!
-
Client(测试3)
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) throws Exception { // 创建游戏玩家 IGamePlayer gpp =new GamePlayer("kungfu~peng"); //代练者 IGamePlayer proxy =gpp.getProxy();// 自己指定代理者 // 登录游戏系统 proxy.login("123", "***"); // 杀野怪 proxy.killBoss(); // 升级 proxy.upgrade(); } } //结果: kungfu~peng登录了! kungfu~peng在打怪! kungfu~peng又升一级!
-
-
类图
- 强制代理的概念就是要从真正角色查找到代理角色,不允许直接访问真实访问真实角色。高层模块只要调用getProxy()就可以访问真实角色的所有方法,它根本就不需要产生一个代理类出来,代理类管理已经由真实角色自己完成
-
代理类是有个性的
- 代理模式不仅仅可以实现主题接口,也可以实现主题接口所没有的方法,而代理的目的性就是在主题接口的同时增强其作用,这种增强的本质就是对目标方法的拦截和过滤
-
例如代练打游戏要收费,升一级要5元
- 类图
-
改动的类
-
IProxy
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public interface IProxy { // 计算费用 public void count(); }
-
GamePlayProxy
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayerProxy implements IGamePlayer, IProxy { 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(); this.count(); } @Override public IGamePlayer getProxy() { return this; } @Override public void count() { System.out.println("消费5元!"); } }
- 执行效果
kungfu~peng登录了! kungfu~peng在打怪! kungfu~peng又升一级! 消费5元!
-
- 类图
动态代理
- 在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。
- 面向切面编程--AOP--Aspect Oriented Programming
-
类图
- 在类中增加了一个InvocationHandler接口和GamePlayIH类,作用就是生成一个对象的代理对象,其中的InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理
-
代码
- InvocationHandler:JDK自带的接口,这个不用手动写
-
GamePlayIH
package com.peng.game; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayIH implements InvocationHandler { // 被代理者 Class cls = null;//感觉这个多余... // 被代理的实例 Object obj = null; // 我要代理谁 public GamePlayIH(Object obj) { super(); this.obj = obj; } // 调用被代理的方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(this.obj, args); return result; } }
- 解释:invoke是接口InvocationHandler定义必须实现的方法,它完成对真实方法的调用。动态代理会宣称“我们已经实现了该接口的所有方法”,动态代理怎样才能实现被代理接口中的方法呢?
- 默认情况下所有的方法返回值都是空的,使得,代理已经实现它了,但是没有任何的逻辑含义,怎么办?
- 好办,通过InvocationHandler接口,所有的方法都由Handler来处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务
- 默认情况下所有的方法返回值都是空的,使得,代理已经实现它了,但是没有任何的逻辑含义,怎么办?
-
IGamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public interface IGamePlayer { // 登录系统 public void login(String username, String password); // 杀怪 public void killBoss(); // 升级 public void upgrade(); // 返回代理 public IGamePlayer getProxy(); }
-
GamePlayer
package com.peng.game; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayer implements IGamePlayer { private String name = ""; private IGamePlayer proxy = null; public GamePlayer(String _name) throws Exception { super(); this.name = _name; } @Override public void login(String username, String password) { if (isProxy()) { System.out.println(username + "登录了!"); } else { System.out.println("请使用指定的代理访问!"); } } @Override public void killBoss() { if (isProxy()) { System.out.println(this.name + "在打怪!"); } else { System.out.println("请使用指定的代理访问!"); } } @Override public void upgrade() { if (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; } } }
-
场景类Client
package com.peng.game; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client_IH { public static void main(String[] args) throws Exception { // 定义一个痴迷的玩家 IGamePlayer player = new GamePlayer("kungfu~peng"); // 定义一个Handler InvocationHandler handler = new GamePlayIH(player); // 获得类的class loader ClassLoader cl = player.getClass().getClassLoader(); // 动态产生一个代理者 IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl, new Class[] { IGamePlayer.class }, handler); // 注意:这里还是用的强制代理---其他代码用强制代理模式 proxy = proxy.getProxy(); // 登录 proxy.login("kungfu~peng", "***"); // 开始杀怪 proxy.killBoss(); // 升级 proxy.upgrade(); } }
-
执行结果
kungfu~peng登录了! kungfu~peng在打怪! kungfu~peng又升一级! 消费5元!
-
解释:动态代理模式中,既没有创建代理类,也没有实现IGamePlayer接口,这就是动态代理,别急,动态代理可不仅仅这么多内容,还有更重要的,如果想让游戏登录后就发一个信息给我们,防止账号被盗用。怎么办?当然是修正GamePlayIH类啦!!
-
代码如下
package com.peng.game; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class GamePlayIH implements InvocationHandler { // 被代理者 Class cls = null; // 被代理的实例 Object obj = null; // 我要代理谁 public GamePlayIH(Object obj) { super(); this.obj = obj; } // 调用被代理的方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(this.obj, args); // 如果是登录方法则发送信息 if (method.getName().equalsIgnoreCase("login")) { System.out.println("有人用我的账号登录!"); } return result; } }
-
-
注:这里测试不能用强制代理了,因为获取的代理获取的是getProxy方法
package com.peng.game; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client_IH { public static void main(String[] args) throws Exception { // 定义一个痴迷的玩家 IGamePlayer player = new GamePlayer("kungfu~peng"); // 定义一个Handler InvocationHandler handler = new GamePlayIH(player); // 获得类的class loader ClassLoader cl = player.getClass().getClassLoader(); // 动态产生一个代理者 IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(cl, new Class[] { IGamePlayer.class }, handler); // 这里还是用的强制代理 proxy = proxy.getProxy(); // 登录 proxy.login("kungfu~peng", "***"); // 开始杀怪 proxy.killBoss(); // 升级 proxy.upgrade(); } } //测试结果 getProxy kungfu~peng登录了! kungfu~peng在打怪! kungfu~peng又升一级! 消费5元!
- 知道结果了,也通过这个意料之外的运行更加清楚动态代理模式,现在你自己手动改下吧!
- AOP的编程没有使用什么新的技术,但是它对我们的设计、编码也有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去
- 动态代理的通用类图
-
- 两条独立发展的线路,动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的耦合功能。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装的任务
-
代码
-
Subject
package com.peng.dynamicproxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public interface Subject { // 业务操作 public void doSomething(String str); }
-
RealSubject
package com.peng.dynamicproxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class RealSubject implements Subject { @Override public void doSomething(String str) { // 业务操作 System.out.println("do Something~~" + str); } }
-
MyInvocationHandler
package com.peng.dynamicproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class MyInvocationHandler implements InvocationHandler { // 被代理的对象 private Object target = null; // 通过构造函数传递一个对象 public MyInvocationHandler(Object _obj) { super(); this.target = _obj; } // 代理方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(this.target, args); } }
-
DynamicProxy
package com.peng.dynamicproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ 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); } }
-
IAdvice
package com.peng.dynamicproxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public interface IAdvice { // 通知方法只有一个 public void exec(); }
-
BeforeAdvice
package com.peng.dynamicproxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class BeforeAdvice implements IAdvice { @Override public void exec() { System.out.println("执行了前置函数!"); } }
-
Client
package com.peng.dynamicproxy; import java.lang.reflect.InvocationHandler; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ 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("Finish!!"); } }
- 执行效果
执行了前置函数! do Something~~Finish!!
-
-
动态代理过程
-
继续扩展DynamicProxy类之SubjectDynamicProxy:
package com.peng.dynamicproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class SubjectDynamicProxy extends DynamicProxy { public static <T> T newProxyInstance(Subject subject) { // 获得ClassLoader ClassLoader loader = subject.getClass().getClassLoader(); // 过的接口数组 Class<?>[] classes = subject.getClass().getInterfaces(); // 获得Handler InvocationHandler handler = new MyInvocationHandler(subject); // 寻找JoinPoint连接点,AOP框架使用元数据定义 return newProxyInstance(loader, classes, handler); } }
-
测试类更加简洁
package com.peng.dynamicproxy; /** * @author kungfu~peng * @data 2017年11月17日 * @description */ public class Client { public static void main(String[] args) { // 定义一个主题 Subject subject = new RealSubject(); // 定义主题的代理 Subject proxy = SubjectDynamicProxy.newProxyInstance(subject); // 代理行为 proxy.doSomething("Finish!!"); } }
注意
- 与静态代理区别:加入切面(切入)
- 实现动态代理的首要条件:被代理类实现一个接口
最佳实践
- AOP
- AspectJ
- 弄清名词:
- 切面(Aspect)
- 切入点(JoinPoint)
- 通知(Advice)
- 织入(Weave)
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正。