代理模式
代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
- Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,无特殊要求。
- RealSubject具体主题角色:也叫被委托角色、被代理角色。是业务逻辑的具体执行者。
- Proxy代理主题角色:也叫委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
抽象主题类Subject:
public interface Subject {
//定义一个方法
public void request();
}
真实主题类RealSubject:
public class RealSubject implements Subject {
//实现方法
public void request() {
//业务逻辑处理
}
}
代理类Proxy:
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
public Proxy(){
this.subject = new Proxy();
}
//通过构造函数传递代理者
public Proxy(Subject _subject){
this.subject = _subject;
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something
}
//善后处理
private void after(){
//do something
}
}
通常情况下,一个接口只需要一个代理类就可以了,具体代理哪个实现类由高层模块来决定,也就是在代理类的构造函数中传递被代理者。
代理模式的优点
- 职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
- 高扩展性:具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,我们的代理类完全可以在不做任何修改的情况下使用。
- 智能化。
代理模式的使用场景
- 打官司找律师,事前调查、事后追查都由律师来搞定,这就是为了减轻你的负担。
- 代理模式的使用场景非常多,Spring AOP就是一个非常典型的动态代理。
代理模式的扩展
- 非代理模式(即不使用代理模式)
- 代理模式
- 普通代理模式:用户要知道代理的存在,然后才能访问。
- 强制代理模式:调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。
- 有附加条件的代理
- 动态代理
实例分析
我们通过网络游戏升级打怪的例子对代理模式的从无到有进行简单梳理。
(1)非代理模式(即不使用代理模式)
网游最常用的功能:登录游戏、杀怪和升级。我们可以得到如下类图:
public interface IGamePlayer {
public void login(String user,String password); //登录
public void killBoss(); //杀怪
public void upgrade(); //升级
}
import com.sfq.impl.IGamePlayer;
public class GamePlayer implements IGamePlayer {
private String name = "";
public GamePlayer(String _name) {
this.name = _name;
}
@Override
public void login(String user, String password) {
System.out.println("登录名为 "+user+" 的用户 "+this.name+" 登录成功!");
}
@Override
public void killBoss() {
System.out.println(this.name+" 在打怪!");
}
@Override
public void upgrade() {
System.out.println(this.name+" 又升了一级!");
}
}
import com.sfq.action.GamePlayer;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个玩家
IGamePlayer player = new GamePlayer("张三");
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
player.login("zhangsan", "password"); //登录
player.killBoss(); //杀怪
player.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
张三 在打怪!
张三 又升了一级!
结束时间是:2020-5-4 23:00
有时候我们玩游戏,每天都一样的打怪升级打怪升级,无限循环。如果我们既不想玩,又想游戏升级怎么办呢?代练啊(当然,这里针对设计模式举例,并不支持请代练)。同样,这里可以加入一个代理类。
(2)代理模式
加入代理类GamePlayerProxy后的类图如上图所示。此时,我们的可以通过场景直接访问代理类,通过代理类执行GamePlayer类的动作。
import com.sfq.impl.IGamePlayer;
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer = _gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
通过代理类实现IGamePlayer接口,每个方法都调用gamePlayer中的对应方法,来实现代理功能。
import com.sfq.action.GamePlayer;
import com.sfq.action.GamePlayerProxy;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个玩家
IGamePlayer player = new GamePlayer("张三");
//定义一个代练
GamePlayerProxy proxy = new GamePlayerProxy(player);
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
proxy.login("zhangsan", "password"); //登录
proxy.killBoss(); //杀怪
proxy.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
张三 在打怪!
张三 又升了一级!
结束时间是:2020-5-4 23:00
(3)普通代理模式:用户要知道代理的存在,然后才能访问。
相对于(2)中的代理模式,将代理类和玩家类两个实现类的构造函数进行重新设计。实现代码如下:
import com.sfq.impl.IGamePlayer;
public class GamePlayer implements IGamePlayer {
private String name = "";
public GamePlayer(IGamePlayer _gamePlayer, String _name) throws Exception{
if (_gamePlayer == null) {
throw new Exception("不能创建真实角色!");
} else {
this.name = _name;
}
}
@Override
public void login(String user, String password) {
System.out.println("登录名为 "+user+" 的用户 "+this.name+" 登录成功!");
}
@Override
public void killBoss() {
System.out.println(this.name+" 在打怪!");
}
@Override
public void upgrade() {
System.out.println(this.name+" 又升了一级!");
}
}
import com.sfq.impl.IGamePlayer;
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(String name) {
try {
gamePlayer = new GamePlayer(this, name);
} catch (Exception e) {
// TODO: handle exception
}
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
import com.sfq.action.GamePlayerProxy;
public class Client {
public static void main(String[] args) {
//定义一个代练
GamePlayerProxy proxy = new GamePlayerProxy("张三");
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
proxy.login("zhangsan", "password"); //登录
proxy.killBoss(); //杀怪
proxy.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
张三 在打怪!
张三 又升了一级!
结束时间是:2020-5-4 23:00
(4)强制代理模式:调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。
通过真实角色查找到代理角色,否则你不能访问。比如:你要找明星签约,明星让你联系其经纪人。本来想绕过代理直接找明星,结果还是得找代理。
我们仅在(2)的基础上进行了一些修改:
- 在IGamePlayer接口上增加了一个getProxy()方法;
- 在真实角色GamePlayer实现getProxy()指定一个自己的代理,并通过isProxy()方法判断是否是代理访问。
public interface IGamePlayer {
public void login(String user,String password); //登录
public void killBoss(); //杀怪
public void upgrade(); //升级
public IGamePlayer getProxy(); //每个人都可以找一下自己的代理
}
import com.sfq.impl.IGamePlayer;
public class GamePlayer implements IGamePlayer {
private String name = "";
private IGamePlayer proxy = null;
public GamePlayer(String _name) {
this.name = _name;
}
@Override
public void login(String user, String password) {
if (this.isProxy()) {
System.out.println("登录名为 "+user+" 的用户 "+this.name+" 登录成功!");
} else {
System.out.println("请使用指定的代理访问");
}
}
@Override
public void killBoss() {
if (this.isProxy()) {
System.out.println(this.name+" 在打怪!");
} 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;
}
}
}
import com.sfq.impl.IGamePlayer;
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer = _gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
@Override
public IGamePlayer getProxy() {
return this;
}
}
★我们首先验证,强制模式是否可以直接访问真实角色。结果:不能。
import com.sfq.action.GamePlayer;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个角色
IGamePlayer player = new GamePlayer("张三");
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
player.login("zhangsan", "password"); //登录
player.killBoss(); //杀怪
player.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
结束时间是:2020-5-4 23:00
★验证,强制模式是否可以随意生产一个代理来访问。结果:不能。
import com.sfq.action.GamePlayer;
import com.sfq.action.GamePlayerProxy;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个角色
IGamePlayer player = new GamePlayer("张三");
//定义代练
IGamePlayer proxy = new GamePlayerProxy(player);
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
proxy.login("zhangsan", "password"); //登录
proxy.killBoss(); //杀怪
proxy.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
结束时间是:2020-5-4 23:00
★验证,强制模式通过指定代理来访问。结果:可以。
import com.sfq.action.GamePlayer;
import com.sfq.action.GamePlayerProxy;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个角色
IGamePlayer player = new GamePlayer("张三");
//定义代练
IGamePlayer proxy = player.getProxy();
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
proxy.login("zhangsan", "password"); //登录
proxy.killBoss(); //杀怪
proxy.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
张三 在打怪!
张三 又升了一级!
结束时间是:2020-5-4 23:00
(5)有附加条件的代理
代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。例如游戏代理是需要收费的,升一级需要5元钱。
该类图是基于(2)中类图中添加了,IProxy()接口来计算代练的费用,此时GamePlayerProxy类需要同时实现两个接口。
public interface IProxy {
//计算费用
public void count();
}
import com.sfq.impl.IGamePlayer;
import com.sfq.impl.IProxy;
public class GamePlayerProxy implements IGamePlayer,IProxy {
private IGamePlayer gamePlayer = null;
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer = _gamePlayer;
}
@Override
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
@Override
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
@Override
public void count() {
System.out.println("升级总费用是:150元");
}
}
import com.sfq.action.GamePlayer;
import com.sfq.action.GamePlayerProxy;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个玩家
IGamePlayer player = new GamePlayer("张三");
//定义一个代练
GamePlayerProxy proxy = new GamePlayerProxy(player);
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
proxy.login("zhangsan", "password"); //登录
proxy.killBoss(); //杀怪
proxy.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
张三 在打怪!
张三 又升了一级!
升级总费用是:150元
结束时间是:2020-5-4 23:00
(6)动态代理
动态代理是在实现阶段不用关心代理对象,而在运行阶段才指定代理哪一个对象。面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。
在类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。因此我们只需要编码GamePlayIH类,并实现接口InvocationHandler即可。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GamePlayIH implements InvocationHandler {
//被代理者
Class cls = null;
//被代理的实例
Object obj = null;
//我要代理谁
public GamePlayIH(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;
}
}
invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。
import com.sfq.action.GamePlayIH;
import com.sfq.action.GamePlayer;
import com.sfq.impl.IGamePlayer;
public class Client {
public static void main(String[] args) {
//定义一个玩家
IGamePlayer player = new GamePlayer("张三");
//定义一个代练
InvocationHandler handler = new GamePlayIH(player);
//开始玩游戏,记下时间戳
System.out.println("开始时间是:2020-5-4 19:08");
//获得类的class loader
ClassLoader c1 = player.getClass().getClassLoader();
//动态产生一个代理者
IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(c1, new Class[]{IGamePlayer.class},handler);
proxy.login("zhangsan", "password"); //登录
proxy.killBoss(); //杀怪
proxy.upgrade(); //升级
System.out.println("结束时间是:2020-5-4 23:00");
}
}
结果
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
张三 在打怪!
张三 又升了一级!
结束时间是:2020-5-4 23:00
通过 InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。
我们既没有创建代理类,也没有实现IGamePlayer接口,这就是动态代理。
如果想让游戏登录后发一个信息给我们,防止账号被人盗用。我们可以直接修改代理类GamePlayIH,在类中增加一个判断:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class GamePlayIH implements InvocationHandler {
//被代理者
Class cls = null;
//被代理的实例
Object obj = null;
//我要代理谁
public GamePlayIH(Object obj) {
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;
}
}
此时,我们场景类运行后的结果为:
开始时间是:2020-5-4 19:08
登录名为 zhangsan 的用户 张三 登录成功!
有人在用我的账号登录!
张三 在打怪!
张三 又升了一级!
结束时间是:2020-5-4 23:00
这就是AOP编程。AOP编程没有使用什么新的技术,但是它对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。
动态代理模型
包含两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。
抽象主题Subject:
public interface Subject {
//业务操作
public void doSomething(String str);
}
真实主题RealSubject:
public class RealSubject implements Subject {
//业务操作
public void doSomething(String str) {
System.out.println("do something!---->" + str);
}
}
动态代理的Handler类MyInvocationHandler:所有通过动态代理实现的方法全部通过invoke方法调用。
public class MyInvocationHandler implements InvocationHandler {
//被代理的对象
private Object target = null;
//通过构造函数传递一个对象
public MyInvocationHandler(Object _obj){
this.target = _obj;
}
//代理方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行被代理的方法
return method.invoke(this.target, args);
}
}
动态代理类DynamicProxy:
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 及实现BeforeAdvice :
public interface IAdvice {
//通知只有一个方法,执行即可
public void exec();
}
public class BeforeAdvice implements IAdvice{
public void exec(){
System.out.println("我是前置通知,我被执行了!");
}
}
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类中,我们有这样的方法:
this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),new MyInvocationHandler(_obj));
该方法是重新生成了一个对象。
c.getInterfaces()是说查找到该类的所有接口,然后实现接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是new MyInvocationHandler(_Obj)这个对象。于是我们知道一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现。
动态代理调用过程: