代理模式
代理模式(Proxy Pattern)是一种使用率非常高的模式,在java的多种开源框架中都有涉及,如Spring Aop 、Mybatis等我们常见开发框架中都有他们的身影。
其定义是为其他对象提供一种代理以控制对这个对象的访问,那么在实际的开发运用中则是对某一个对象的提供的执行方法,在横向的基础上做一个加持。使之能够得到更强大的功能体现。
接下来我们以一个简单的例子来体现代理模式的核心思想
-
场景:假设我们有一个游戏系统,我们可以能在这个游戏中登录账号、充值、打怪、退出账号等操作。如果我们觉得这一系列操作太繁琐,我们需要开发一套外挂程序让它来帮助我们完成这一系列的操作。然后游戏自动升级。
-
我们首先定义一个游戏玩家接口,定义这个玩家是需要有登录账号、充值、打怪、退出账号这些操作的。
public interface IGamePlayer { // 登录游戏 void login(String name, String pwd); // 充值 void recharge(Integer money); // 练级 void playBoss(); // 登出 void logOut(String name); }
-
再定义其实现类,游戏玩家。
public class GamePlayer implements IGamePlayer { private String name; public GamePlayer(String name) { this.name = name; } @Override public void login(String name, String pwd) { if (Objects.deepEquals(this.name, name)) { System.out.println(name + "用户登录游戏,登录密码:" + pwd); } else { System.out.println("用户登录失败,用户名不存在"); } } @Override public void recharge(Integer money) { System.out.println(this.name + "用户充值额:" + money); } @Override public void playBoss() { System.out.println(this.name + "开始打怪!"); } @Override public void logOut(String name) { System.out.println(this.name + "登出了游戏"); } }
-
接下来我们定义一个外挂类,既然是外挂它也需要有操作游戏的功能,所以它也需要实现IGamePaler类,而且他还需要委托的角色,指的是它需要具体帮谁去处理业务。
public class GamePlayerProxy implements IGamePlayer { // 委托角色 private IGamePlayer iGamePlayer; public GamePlayerProxy(IGamePlayer iGamePlayer) { this.iGamePlayer = iGamePlayer; } @Override public void login(String name, String pwd) { System.out.println(">>>日志:" + name + "用户登录了游戏"); this.iGamePlayer.login(name, pwd); } @Override public void recharge(Integer money) { this.iGamePlayer.recharge(money); } @Override public void playBoss() { this.iGamePlayer.playBoss(); } @Override public void logOut(String name) { System.out.println(">>>日志:" + name + "用户登出了游戏"); this.iGamePlayer.logOut(name); } }
-
模拟用户使用场景
public class Test { public static void main(String[] args) { // 游戏启动 GamePlayer ruandy = new GamePlayer("ruandy"); // 开启外挂 GamePlayerProxy proxy = new GamePlayerProxy(ruandy); // 外挂操作 proxy.login("ruandy","12345"); proxy.recharge(100); proxy.playBoss(); proxy.logOut("ruandy"); } }
-
运行结果:
>>>日志:ruandy用户登录了游戏
ruandy用户登录游戏,登录密码:12345
ruandy用户充值额:100
ruandy开始打怪!
>>>日志:ruandy用户登出了游戏
ruandy登出了游戏
以上代码,我们通过一个简单的方式实现了某个游戏的外挂功能。外挂帮助我们完成了游戏的具体操作的同时,还帮助我们打印出了游戏玩家的登录登出日志。
这里我们可以分析下,在实际我们开发过程中,游戏玩家部分则是我们开发时需要具体实现的业务逻辑。外挂部分则是业务逻辑部分需要的加持,我们不仅让外挂程序帮助我们把需要做的事情做了,并且我们还可以自定义功能让其帮助我们做一些其他的事情。
这就是一个最简单的代理模式的运用。如上述例子,参与的三个角色中抽象出来的类图如下:
-
Subject:抽象主题角色,也就是IGamePlayer类,定义了一系列的功能接口,是一个最普通的业务定义类型。
-
RealSubject: 具体实现,GamePlayer.
-
Proxy: 相当于示例中的外挂。在这里是代理类, 也叫委托类。它是真实负责处理业务的类。
我们根据上面的类图,把我们的示例程序进一步精简下public interface Subject { void request(); } // 具体主题 public class RealSubject implements Subject { @Override public void request() { // 具体的业务代码 } } // 抽象类、委托类定义 public class Proxy implements Subject { private Subject subject; 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 class Test2 { public static void main(String[] args) { // 获得具体主题角色 RealSubject realSubject = new RealSubject(); // 创建委托对象 Proxy proxy = new Proxy(realSubject); proxy.request(); } }
通过精简后的代码我们为一个普通的请求函数,赋予了该函数执行前和执行后的处理功能。在不影响原有功能的前提下对其做了横向的功能拓展。
代理模式的优点
-
职责清晰:
我们之前所说的一直都是横向拓展,是不影响原有功能的前提下作的拓展。对于职责的区分则是业务逻辑只关心业务逻辑该关心的部分,而一些如前置或者善后的工作则由代理类去完成。如上述例子中记录日志、执行前、执行后的相关部分。 -
高拓展性:
具体的主题实现类肯定会一直变化,因为它是业务部分代码,将来会随之业务的更替也发生改变,不管怎么改只要它实现了我们的主题(Subject)接口,代理类都会控制它。如果产生了新的主体实现,也没有关系,它也只需要实现主题接口即可。 -
智能化:
这里的智能化在上述代码中还未体现出来。接下我们可以了解下动态代理的相关实现后就会体现。
普通代理、强制和动态代理
普通代理
到这里我们的示例代码展示的其实相对于代理模式来说只是一个最基本的模型。里面的结构还是存在一些缺陷,我们一步一步将其改造完善。
-
缺陷一:一般既然使用代理类了,一些基本的操作是不用主题类再去操作了包括主题对象的创建,这里也可以让代理帮忙创建还是需要对代码稍作修改。
public interface IGamePlayer { // 登录游戏 void login(String name, String pwd); // 充值 void recharge(Integer money); // 练级 void playBoss(); // 登出 void logOut(String name); } public class GamePlayer implements IGamePlayer { private String name; public GamePlayer(IGamePlayer proxy, String name) throws Exception { if (proxy instanceof IGamePlayer) { this.name = name; } else { throw new Exception("不是规定的代理类,不得创建代理"); } } @Override public void login(String name, String pwd) { if (Objects.deepEquals(this.name, name)) { System.out.println(name + "用户登录游戏,登录密码:" + pwd); } else { System.out.println("用户登录失败,用户名不存在"); } } @Override public void recharge(Integer money) { System.out.println(this.name + "用户充值额:" + money); } @Override public void playBoss() { System.out.println(this.name + "开始打怪!"); } @Override public void logOut(String name) { System.out.println(this.name + "登出了游戏"); } } public class GamePlayerProxy implements IGamePlayer { private IGamePlayer iGamePlayer; public GamePlayerProxy(String name) { GamePlayer gamePlayer = null; try { gamePlayer = new GamePlayer(this,name); } catch (Exception e) { e.printStackTrace(); } this.iGamePlayer = gamePlayer; } @Override public void login(String name, String pwd) { System.out.println(">>>日志:" + name + "用户登了游戏"); this.iGamePlayer.login(name, pwd); } @Override public void recharge(Integer money) { this.iGamePlayer.recharge(money); } @Override public void playBoss() { this.iGamePlayer.playBoss(); } @Override public void logOut(String name) { System.out.println(">>>日志:" + name + "用户登出了游戏"); this.iGamePlayer.logOut(name); } } public class Test { public static void main(String[] args) { GamePlayerProxy proxy = new GamePlayerProxy("ruandy"); proxy.login("ruandy","12345"); proxy.recharge(100); proxy.playBoss(); proxy.logOut("ruandy"); } }
这里我们将具体的主题对象交由代理去执行。我们只需要定义代理即可,对于代码结构上来说,位于上层的代码是不知道具体业务逻辑实现的,也感知不到具体的实现存在。我们只需要知道有这样一个代理为我们提供了哪些服务即可。这是基于代理模式的一种变形。我们称之为普通代理。
强制代理
- 缺陷二:对于我们的具体主题实现来说,就算是使用了代理,但是我们还是可以通过实例化具体主题来直接调用真实的服务
如下代码
IGamePlayer player= new GamePlayer("ruandy");
player.login("ruandy","12345");
player.logOut("ruandy");
而我们使用代理的初衷可能是,一切尽由代理帮忙处理,其他的访问真实主题角色的方式一概不允许。我们再对代码做一下调整。
public interface IGamePlayer {
// 登录游戏
void login(String name, String pwd);
// 充值
void recharge(Integer money);
// 练级
void playBoss();
// 登出
void logOut(String name);
// 自己指定代理类
IGamePlayer getProxy();
}
public class GamePlayer implements IGamePlayer {
private String name;
private IGamePlayer proxy;
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String name, String pwd) {
if (!this.hasProxy()) {
System.out.println("请使用指定代理类访问");
return;
}
if (Objects.deepEquals(this.name, name)) {
System.out.println(name + "用户登录游戏,登录密码:" + pwd);
} else {
System.out.println("用户登录失败,用户名不存在");
}
}
@Override
public void recharge(Integer money) {
if (!this.hasProxy()) {
System.out.println("请使用指定代理类访问");
return;
}
System.out.println(this.name + "用户充值额:" + money);
}
@Override
public void playBoss() {
if (!this.hasProxy()) {
System.out.println("请使用指定代理类访问");
return;
}
System.out.println(this.name + "开始打怪!");
}
@Override
public void logOut(String name) {
if (!this.hasProxy()) {
System.out.println("请使用指定代理类访问");
return;
}
System.out.println(this.name + "登出了游戏");
}
@Override
public IGamePlayer getProxy() {
this.proxy = new GamePlayerProxy(this);
return this.proxy;
}
// 验证是否由代理类调用
private boolean hasProxy() {
if (null == this.proxy) {
return false;
}
return true;
}
}
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer iGamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.iGamePlayer = gamePlayer;
}
@Override
public void login(String name, String pwd) {
System.out.println(">>>日志:" + name + "用户登录了游戏");
this.iGamePlayer.login(name, pwd);
}
@Override
public void recharge(Integer money) {
this.iGamePlayer.recharge(money);
}
@Override
public void playBoss() {
this.iGamePlayer.playBoss();
}
@Override
public void logOut(String name) {
System.out.println(">>>日志:" + name + "用户登录了游戏");
this.iGamePlayer.logOut(name);
}
@Override
public IGamePlayer getProxy() {
return this;
}
}
public class Test {
public static void main(String[] args) {
IGamePlayer ruandy = new GamePlayer("ruandy");
GamePlayerProxy proxy = new GamePlayerProxy(ruandy);
proxy.login("ruandy","12345");
proxy.recharge(100);
proxy.playBoss();
proxy.logOut("ruandy");
}
}
如上代码,我们为具体的实现类添加了指定代理的方法。且对于每个实现方法中都加了对访问来源的控制。如果不是有代理访问的都不会做处理
执行结果
>>>日志:ruandy用户登录了游戏
请使用指定代理类访问
请使用指定代理类访问
请使用指定代理类访问
>>>日志:ruandy用户登出了游戏
请使用指定代理类访问
正常执行方式
public class Test {
public static void main(String[] args) {
IGamePlayer ruandy = new GamePlayer("ruandy");
IGamePlayer proxy = ruandy.getProxy();
proxy.login("ruandy","12345");
proxy.recharge(100);
proxy.playBoss();
proxy.logOut("ruandy");
}
}
执行结果
>>>日志:ruandy用户登录了游戏
ruandy用户登录游戏,登录密码:12345
ruandy用户充值额:100
ruandy开始打怪!
>>>日志:ruandy用户登录了游戏
ruandy登出了游戏
通过以上改造我们得到了另外一种代理的模式,这种方式不需要调用者显示的产生一个代理,而是与具体主题角色产生。高层模块只需要调用getProxy()方法则可以调用所有的托管类服务了。
这种方式即强制代理模式,我们既然将具体主题委托了代理托管,就不应该在留有其他的方式来访问具体实现。这种方式相对于普通代理模式来说要更符合实际一点。
动态代理
通过前面的代理模式的基本代码及后面的普通代理、强制代理的代码演进实际都是为了最后我们的动态代理做引子。
我们再次回顾之前的代码。有个让人难以接受的地方一直存在。就是代理类也必须实现主题接口的所有方法。如果之后主题类添加了方法,如getProxy()方法,代理类中也需要同步增加。这样对于代码拓展来说也不是特别方便。我们有没有一种方案,让代理类不需要知道他所代理的委托对象的具体实现,也就是不需要关心代理谁,而是在代码运行的时候动态的指定他的委托对象。从而实现代理。
动态代理就是为实现以上需求而来,但是我们在使用动态代理前需要先了解下JDK的一个知识点:
InvacationHandler: 这是JDK为我们提供的一个动态代理接口,实现动态代理我们只需要实现它就可以了。我们可以理解为,实现了这个接口那么就标识这这个实现类就有了委托对象的所有方法。
通过以下代码展示动态代理的实现:
首先还是定义主题接口
public interface IGamePlayer {
// 登录游戏
void login(String name, String pwd);
// 充值
void recharge(Integer money);
// 练级
void playBoss();
// 登出
void logOut(String name);
}
主题具体实现
public class GamePlayer implements IGamePlayer {
private String name;
public GamePlayer(String name) {
this.name = name;
}
@Override
public void login(String name, String pwd) {
if (Objects.deepEquals(this.name, name)) {
System.out.println(name + "用户登录游戏,登录密码:" + pwd);
} else {
System.out.println("用户登录失败,用户名不存在");
}
}
@Override
public void recharge(Integer money) {
System.out.println(this.name + "用户充值额:" + money);
}
@Override
public void playBoss() {
System.out.println(this.name + "开始打怪!");
}
@Override
public void logOut(String name) {
System.out.println(this.name + "登出了游戏");
}
}
实现JDK提供的InvocationHandle接口,定义代理执行类(handle)
public class GamePlayerProxy implements InvocationHandler {
private Object obj;
public GamePlayerProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“委托代理执行中");
Object invoke = method.invoke(this.obj, args);
return invoke;
}
}
public class Test {
public static void main(String[] args) {
// 定义玩家角色
IGamePlayer ruandy = new GamePlayer("ruandy");
// 定义代理handle
InvocationHandler gamePlayerProxy = new GamePlayerProxy(ruandy);
Class[] classes = {IGamePlayer.class};
ClassLoader classLoader = ruandy.getClass().getClassLoader();
// 动态产生代理者
IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, classes, gamePlayerProxy);
proxy.login("ruandy", "12345");
proxy.recharge(100);
proxy.playBoss();
proxy.logOut("ruandy");
}
}
执行结果
委托代理执行中
ruandy用户登录游戏,登录密码:12345
委托代理执行中
ruandy用户充值额:100
委托代理执行中
ruandy开始打怪!
委托代理执行中
ruandy登出了游戏
通过代码展示,我们可以看到动态代理与前面我们展示的其他代理来说最大的区别就是我们没有创建代理类,而是被我们的处理类代替了,且也没有实现IGamePalyer接口。
接下来我们再重新把代码在优化下,如下这里加粗部分代码,虽然实现了动态代理部分,但是我们任然有很多写死的地方如我们动态产生代理者是不是完全可以交由一个专门的模块去生成呢?
public class Test {
public static void main(String[] args) {
// 定义玩家角色
IGamePlayer ruandy = new GamePlayer("ruandy");
// 定义代理handle
InvocationHandler gamePlayerProxy = new GamePlayerProxy(ruandy);
Class[] classes = {IGamePlayer.class};
ClassLoader classLoader = ruandy.getClass().getClassLoader();
// 动态产生代理者
IGamePlayer proxy = (IGamePlayer) Proxy.newProxyInstance(classLoader, classes, gamePlayerProxy);
proxy.login("ruandy", "12345");
proxy.recharge(100);
proxy.playBoss();
proxy.logOut("ruandy");
}
}
我们可以定义一个动态生成代理的泛型类来专门处理这个业务,不仅能够专门处理动态代理的生成,还能执行特殊功能。
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) {
System.out.println("执行部分代理前置任务。。。");
return (T) Proxy.newProxyInstance(loader, interfaces, handler);
}
}
然后改造测试部分代码
public class Test {
public static void main(String[] args) {
// 定义玩家角色
IGamePlayer ruandy = new GamePlayer("ruandy");
// 定义代理handle
InvocationHandler gamePlayerProxy = new GamePlayerProxy(ruandy);
Class[] classes = {IGamePlayer.class};
ClassLoader classLoader = ruandy.getClass().getClassLoader();
// 动态产生代理者
GamePlayer proxy = DynamicProxy.newProxyInstance(classLoader, classes, gamePlayerProxy);
proxy.login("ruandy", "12345");
proxy.recharge(100);
proxy.playBoss();
proxy.logOut("ruandy");
}
}
执行结果
执行部分代理前置任务。。。
委托代理执行中
ruandy用户登录游戏,登录密码:12345
委托代理执行中
ruandy用户充值额:100
委托代理执行中
ruandy开始打怪!
委托代理执行中
ruandy登出了游戏
代码到这里,我们可能会有一个疑问,动态代理中既然没有了代理类只有一个执行类,既没有定义代理类,也没有实现主题接口的任何方法。那么我们的具体主题实现时怎么委托给代理的呢。
通过代码我们可以知道,代理类的产生肯定是在这句代码中
动态产生代理者
IGamePlayer proxy = DynamicProxy.newProxyInstance(classLoader, classes, gamePlayerProxy);
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) {
System.out.println("执行部分代理前置任务。。。");
return (T) Proxy.newProxyInstance(loader, interfaces, handler);
}
}
我们追踪Proxy.newProxyInstance(…)源码可以追踪到如下代码
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
在getProxyCLass0(loader,intfs);这里完成了动态代理的工作。如果我们换成中存在具体主题对象的多台代理则直接取出使用,如果没有则会自动生成一个实现了主题接口的代理类,并交由缓存管理。具体的自动生成的代理类为如下:
public final class $Proxy0 extends Proxy implements IGamePlayer {
private static Method m1;
private static Method m5;
private static Method m2;
private static Method m4;
private static Method m6;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void playBoss() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m5 = Class.forName("cn.chileme.section3.IGamePlayer").getMethod("playBoss");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("cn.chileme.section3.IGamePlayer").getMethod("recharge", Class.forName("java.lang.Integer"));
m6 = Class.forName("cn.chileme.section3.IGamePlayer").getMethod("logOut", Class.forName("java.lang.String"));
m3 = Class.forName("cn.chileme.section3.IGamePlayer").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
我们通过如上代码可以看到实际上是与我们之前普通代理模式下定义的代理类的结构是一致的。且动态生成的代理类中,将每个方法都对应了动态代理类中的m1 -m5方法。
到此我们的代理模式的基本的结构及用法就全部罗列完毕。
实际上在我们实际的开发过程中,我们没有必要自己重新造轮子,自己实现一套基于动态代理的框架。如Spring 的Aop就已经很好的帮我们实现了这一需求,且它也是基于动态代理的模式来开发的。也是需要我们上述的几个对象,主题对象(我们需要实现的业务逻辑部分)、代理类(被委托的业务部分,如记录日志、计算金额等业务操作)。