代理模式
-
代理模式:也被称为委托模式,为其他对象提供一种代理以控制对这个对象的访问(Provide a surrogate or placeholder for another object to control access to it)。
- 代理模式的通用类图中包含三种角色,抽象主体角色(Subject)、具体主题角色(RealSubject)、代理主题角色(Proxy),其通用类图如下:
- Subject抽象主体角色:可以使抽象类也可以是接口,是一个普通的业务类型定义。
- RealSubject具体主题角色:也叫做被委托角色、被代理角色,是业务逻辑的具体执行者。
- Proxy代理主题角色:也叫做委托类、代理类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完成前后做预处理和后处理工作
- 代理模式通用类图的代码如下:
-
public interface Subject{ public void request(); } public class RealSubject implements Subject{ @Override public void request() { // TODO Auto-generated method stub //具体的业务逻辑处理 } } public class Proxy implements Subject{ private Subject subject = null; public Proxy(Subject subject){ this.subject = subject; } @Override public void request() { // TODO Auto-generated method stub this.before(); subject.request(); this.after(); } private void after() { //预处理 } private void before() { //后处理 } }
-
代理模式的优点
- 职责清晰:真实角色就是实现实际的业务逻辑,不用关心其他非本职工作的事务,通过后期代理完成非本职工作。
- 高扩展性:具体主题角色可以随时发生变化,只要它实现了接口,而不用关心它如何变化,只要接口没变,代理类可以在完全不做修改的情况下使用。
- 智能化:通过动态代理的方式实现在编码阶段不需要知道代理的对象。
-
代理模式的具体应用
- 普通代理模式,在该模式下调用者只知道代理而不用知道真实角色,屏蔽了真实角色的变更对高层模块的影响,在实际项目中,一般都是通过约定来禁止new一个真实的角色。
- 普通代理模式的实例类图如下:
-
- 具体源码如下,通过GamePlayerProxy的构造函数中创建被代理者对象。
-
public interface IGamePlayer { public void login(String userName, String passwd); public void killBoss(); public void upgrade(); } public class GamePlayer implements IGamePlayer{ public GamePlayer(IGamePlayer _proxy) throws Exception{ if(_proxy == null) //检查代理类是否能创建真是角色对象 throw new Exception("该代理不成代理该类"); } @Override public void login(String userName, String passwd) { // TODO Auto-generated method stub System.out.println(userName + "登入游戏"); } @Override public void killBoss() { // TODO Auto-generated method stub System.out.print("在打怪"); } @Override public void upgrade() { // TODO Auto-generated method stub System.out.println("升级"); } } public class GamePlayerProxy implements IGamePlayer{ private IGamePlayer gameplayer = null; public GamePlayerProxy(){ try { this.gameplayer = new GamePlayer(this); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void login(String userName, String passwd) { // TODO Auto-generated method stub this.gameplayer.login(userName, passwd); } @Override public void killBoss() { // TODO Auto-generated method stub this.gameplayer.killBoss(); } @Override public void upgrade() { // TODO Auto-generated method stub this.gameplayer.upgrade(); } } public class Client{ public static void main(String[] args){ IGamePlayer proxy = new GamePlayerProxy(); proxy.login("Danny", "passwd"); proxy.killBoss(); proxy.upgrade(); } }
- 强制代理模式,该模式是通过真实角色查找到代理模式,其通用类图比普通代理类图只在接口处增加了getProxy()方法,其实例类图如下:
-
- 具体源码如下,其中通过GamePlayer的getProxy()方法取得代理,并且在GamePlayer私有方法isProxy()中增加了判断是否是通过代理访问方法
-
public interface IGamePlayer { public void login(String userName, String passwd); public void killBoss(); public void upgrade(); public IGamePlayer getProxy(); } public class GamePlayer implements IGamePlayer{ private IGamePlayer proxy = null; @Override public void login(String userName, String passwd) { // TODO Auto-generated method stub if(this.isProxy()) System.out.println(userName + "登入游戏"); else System.out.println("请通过代理访问游戏"); } @Override public void killBoss() { // TODO Auto-generated method stub if(this.isProxy()) System.out.print("在打怪"); else System.out.println("请通过代理访问游戏"); } @Override public void upgrade() { // TODO Auto-generated method stub if(this.isProxy()) System.out.println("升级"); else System.out.println("请通过代理访问游戏"); } @Override public IGamePlayer getProxy() { // TODO Auto-generated method stub if(this.proxy == null) this.proxy = new GamePlayerProxy(this); return this.proxy; } private boolean isProxy(){ StackTraceElement[] stack = Thread.currentThread().getStackTrace(); for(int i = 0; i < stack.length; i++){ String name = stack[i].getClassName(); if(name.contains("Proxy") && this.proxy != null) return true; } return false; } } public class GamePlayerProxy implements IGamePlayer{ private IGamePlayer gameplayer = null; protected GamePlayerProxy(IGamePlayer player){ this.gameplayer = player; } @Override public void login(String userName, String passwd) { // TODO Auto-generated method stub this.gameplayer.login(userName, passwd); } @Override public void killBoss() { // TODO Auto-generated method stub this.gameplayer.killBoss(); } @Override public void upgrade() { // TODO Auto-generated method stub this.gameplayer.upgrade(); } @Override public IGamePlayer getProxy() { // TODO Auto-generated method stub return this; } } public class Client { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub IGamePlayer player = new GamePlayer(); IGamePlayer proxy = player.getProxy(); proxy.login("Danny", "passwd"); proxy.killBoss(); proxy.upgrade(); } }
- 代理类可以为真实角色预处理消息、过滤消息、转发消息、事后处理等功能。代理类不仅仅可以有自己的运算方法,通常情况下代理的职责并不单一,它可以组合其它的真实角色,也可是实现自己的职责,如计费等。如下是代理实现计费的通用类图:
-
- 动态代理:在实现阶段不用关心代理谁,而在运行阶段才指定代理哪个对象,现在的面向切面编程(Aspect Oriented Programming)就是采用了动态代理的机制。在该类图中增加了一个InvocationHandler接口和GamePlayer类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理的方法进行代理,其具体实例类图如下:
-
- 实例类图的源码如下:
-
public class GamePlayerIH implements InvocationHandler{ //被代理的实例 private Object obj = null; public GamePlayerIH(Object obj){ this.obj = obj; } //返回一个代理对象 public Object bind(){ return Proxy.newProxyInstance(this.obj.getClass().getClassLoader(), this.obj.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) //调用被代理的方法 throws Throwable { // TODO Auto-generated method stub Object result = method.invoke(this.obj, args);
if(method.getName().equalsIgnoreCase("login")){
System.out.println("代练登入游戏");
} return result; } } public class Client{ /** * @param args */ public static void main(String[] args) { IGamePlayer player = new GamePlayer(); IGamePlayer proxy = (IGamePlayer)new GamePlayerIH(player).bind(); proxy.login("zhangsan", "passwd"); proxy.killBoss(); proxy.upgrade(); } } - 动态代理的通用类图如下:
很简单的两条独立发展的路线,动态代理实现代理的职责,具体的业务逻辑RealSubject实现相关的逻辑功能,两者之间没有必然的耦合。通知从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。
-
- 如下是通用类图中的MyInvacationHandler、DynamicProxy和Client的代码:
-
public class MyInvocationHandler implements InvocationHandler{ //被代理的实例 private Object obj = null; public MyInvocationHandler(Object obj){ this.obj = obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub Object result = method.invoke(this.obj, args); return result; } } public class DynamicProxy <T>{ public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){ if(true){ (new BeforeAdvice()).exec(); } return (T) Proxy.newProxyInstance(loader, interfaces, h); } } public class Client { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub IGamePlayer player = new GamePlayer(); MyInvocationHandler handler = new MyInvocationHandler(player); IGamePlayer proxy = DynamicProxy.newProxyInstance(player.getClass().getClassLoader(), player.getClass().getInterfaces(), handler); proxy.killBoss(); } }
动态代理调用过程示意图如下:
-
最佳实践
要实现动态代理的首要条件是:被代理类必须实现一个接口,当然也有很多技术如CGLIB可以再不实现接口的情况下实现动态代理的方式。