定义
为其他对象提供一种代理以控制对这个对象的访问。
通用类图
通用类图中代理模式也叫做委托模式,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。类图中的三个角色:
● Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
● RealSubject具体主题角色
也叫做被委托角色、被代理角色,是业务逻辑的具体执行者。。
● Proxy代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
代理模式通用代码
抽象主题类
public interface Subject {
//定义一个方法
public void request();
}
是一个非常普通的接口,在接口中我们定义了一个方法request来作为方法的代表,RealSubject对它进行实现。
真实主题类
public class RealSubject implements Subject {
//实现方法
public void request() {
//业务逻辑处理
}
}
RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上。
代理类
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
}
}
个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题
角色,是由场景类决定的。在通常情况下,一个接口只需要一个代理类就可以了,具体代理哪个实现类由高层模块来决定,也就是在代理类的构造函数中传递被代理者,
你要代理谁就产生该代理的实例,然后把被代理者传递进来,该模式在实际的项目应用中比较广泛。
优缺点
优点
1.职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰
2.高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱接口,那我们的代理类完全就可以在不做任何修改的情况下使用。
3.智能化。这在我们以上的讲解中还没有体现出来,不过在我们以下的动态代理章节中你就会看到代理的智能化有兴趣的读者。
使用场景
代理模式的使用场景非常多,大家可以看看Spring AOP,这是一个非常典型的动态代理。
注意
普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题
类是可被重用的和可维护的,使用技术约束的方式对系统维护是一种非常不利的因素。
扩展
- 强制代理
你必须通过真实角色查找到代理角色进行访问,否则你不能访问。
通用类图:
在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除 了代理外谁都不能访问。
强制代理接口类
public interface IGamePlayer {
//登录游戏
public void login(String user,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;
}
//找到自己的代理
public IGamePlayer getProxy(){
this.proxy = new GamePlayerProxy(this);
return this.proxy;
}
//打怪,最期望的就是杀老怪
public void killBoss() {
if(this.isProxy()){
System.out.println(this.name + "在打怪!");
}
else{
System.out.println("请使用指定的代理访问");
}
}
//进游戏之前你肯定要登录吧,这是一个必要条件
public void login(String user, String password) {
if(this.isProxy()){
System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
}
else{
System.out.println("请使用指定的代理访问");
}
}
//升级,升级有很多方法,花钱买是一种,做任务也是一种
public void upgrade() {
if(this.isProxy()){
System.out.println(this.name + " 又升了一级!");
}
else{
System.out.println("请使用指定的代理访问");
}
}
//校验是否是代理访问
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;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
//代理的代理暂时还没有,就是自己
public IGamePlayer getProxy(){
return this;
}
}
场景类的调用方法
//直接访问真实角色
/*
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问
*/
public class Client {
public static void main(String[] args)
{
//定义一个游戏的角色
IGamePlayer player = new GamePlayer("A");
player.login("aaa", "aaa");
//开始杀怪
player.killBoss(); //升级
player.upgrade();
}
}
//直接访问真实角色
/*
登录名为A 的用户张三登录成功!
A在打怪!
A 又升了一级!
*/
public class Client {
public static void main(String[] args)
{
//定义一个游戏的角色
IGamePlayer player = new GamePlayer("A");
//然后再定义一个代练者
IGamePlayer proxy = new GamePlayerProxy(player);
proxy.login("aaa", "aaa");
//开始杀怪
proxy.killBoss(); //升级
proxy.upgrade();
}
}
强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。
代理类功能拓展
一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。
代理类的接口
public interface IProxy {
//计算费用
public void count();
}
}
代理类
public class GamePlayerProxy implements IGamePlayer,IProxy {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
//计算费用
public void count(){
System.out.println("升级总费用是:150元"); }
}
}
实现了IProxy接口,同时在upgrade方法中调用该方法,完成费用结算,其他的类都没有 任何改动。
注意
代理类不仅仅是可以有自己的运算方法,通常的情况下代理的职责并不一定单一,它可以组合其他的真实角色,也可以实现自己的职责,比如计算费用。代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系,读者可以自行扩展一下。
- 动态代理
动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。现在有一个非常流行的名称叫做面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。
通用类图:
在类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。 我们来看程序,接口保持不变,实现类也没有变化。
动态代理类
public class GamePlayIH implements InvocationHandler {
//被代理者
Class cls =null;
//被代理的实例
Object obj = null;
//我要代理谁
public GamePlayIH(Object _obj){
this.obj = _obj;
}
//调用被代理的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(this.obj, args);
return result;
}
}
其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。我 们来详细讲解一下InvocationHandler接口,动态代理是根据被代理的接口生成所有的方法, 也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,那各位读者想想看,动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。
场景类
public class Client {
public static void main(String[] args) throws Throwable {
//定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("A");
//定义一个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.login("aaa", "aaa");//登录
//开始杀怪
proxy.killBoss(); //升级
proxy.upgrade();
}
}
我们还是让代练者帮我们打游戏,但是我们既没有创建代理类,也没有实现IGamePlayer 接口,这就是动态代理。别急,动态代理可不仅仅就这么多内容,还有更重要的,如果想让游戏登录后发一个信息给我们,防止账号被人盗用嘛,该怎么处理?直接修改被代理类GamePlayer?这不是一个好办法,好办法如下
场景类
public class GamePlayIH implements InvocationHandler {
//被代理者
Class cls =null;
//被代理的实例
Object obj = null;
//我要代理谁
public GamePlayIH(Object _obj){
this.obj = _obj;
}
//调用被代理的方法
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;
}
}
太棒了!有人用我的账号就发送一个信息,然后看看自己的账号是不是被人盗了,非常好的方法,这就是AOP编程。AOP编程没有使用什么新的技术,但是它对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。既然动态代理是如此诱人,我们来看看通用动态代理模型,类图如图所示:
简单,两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的 逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。我们先来看Subject接口
抽象主题
public interface Subject {
//业务操作
public void doSomething(String str);
}
真实主题
public class RealSubject implements Subject {
//业务操作
public void doSomething(String str) {
System.out.println("do something!---->" + str);
}
}
动态代理的Handler类
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);
}
}
非常简单,所有通过动态代理实现的方法全部通过invoke方法调用。
动态代理类
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);
}
}
在这里插入了较多的AOP术语,如在什么地方(连接点)执行什么行为(通知)。我们在这里实现了一个简单的横切面编程,有经验的读者可以看看AOP的配置文件就会明白这段代码的意义了。我们来看通知Advice,也就是我们要切入的类,接口和实现如代码
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");
}
}
好,所有的程序都看完了,我们回过头来看看程序是怎么实现的。在DynamicProxy类 中,我们有这样的方法:
this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),new MyInvocationHandler(_obj));
该方法是重新生成了一个对象,为什么要重新生成?你要使用代理呀,注意c.getInterfaces()这句话,这是非常有意思的一句话,是说查找到该类的所有接口,然后实现 接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是new MyInvocationHandler(_Obj)这个对象。于是我们知道一个类的动态代理类是这样的一个类, 由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,其动态 调用过程如图所示。
读者可能注意到我们以上的代码还有更进一步的扩展余地,注意看DynamicProxy类,它 是一个通用类,不具有业务意义,如果我们再产生一个实现类是不是就很有意义了呢
具体业务的动态代理
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);
return newProxyInstance(loader, classes, handler);
}
}
场景类
public class Client {
public static void main(String[] args) {
//定义一个主题
Subject subject = new RealSubject();
//定义主题的代理
Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);
//代理的行为
proxy.doSomething("Finish");
}
}
是不是更加简单了?可能读者就要提问了,这样与静态代理还有什么区别?都是需要实现一个代理类,有区别,注意看父类,动态代理的主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为。
注意
要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下前面的分析吧。当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。
再次说明,以上的动态代理是一个通用代理框架。如果你想设计自己的AOP框架,完全可以在此基础上扩展,我们设计的是一个通用代理,只要有一个接口,一个实现类,就可以使用该代理,完成代理的所有功效。
综上
代理模式应用得非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理,稍不留意就用到代理模式。可能该模式是大家接触最多的模式,而且有了AOP大家写代理就更加简单了,有类似Spring AOP和AspectJ这样非常优秀的工具,拿来主义即可!不过,大家 可以看看源代码,特别是调试时,只要看到类似$Proxy0这样的结构,你就应该知道这是一 个动态代理了。
友情提醒,在学习AOP框架时,弄清楚几个名词就成:切面(Aspect)、切入点 (JoinPoint)、通知(Advice)、织入(Weave)就足够了,理解了这几个名词,应用时你 就可以游刃有余了!
如果你觉得小编总结笔记有所用处,请点点关注和粉丝,后续还会不断更新,让我们一起进步!!!