什么是代理模式?

Java突击队大约 14 分钟

什么是代理模式?

代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供 一种代理以控制对这个对象的访问。)

代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户 端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需 要的额外的新服务。

代理模式的通用类图如图12-1所示:

图10-1:代理模式通用类图

在这里插入图片描述
在这里插入图片描述

根据类图,代理模式包含三个角色:

● Subject:抽象主题角色,它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
● RealSubject:具体主题角色也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。
● Proxy:代理主题角色,也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

来看看具体的代码实现。

  • Subject:抽象主题类
    抽象主题类声明了真实主题类和代理类的公共方法,它可以是接口、抽象类或具体类。
    public interface Subject {
    	//定义了一个方法
       public  void request();
    }
  • RealSubject:真实主题类
    真实主题类继承了抽象主题类,提供了业务方法的具体实现
    public class RealSubject implements Subject{
    
    	//实现方法
    	@Override
    	public void request() {
    		//具体逻辑
    	}
    
    }
  • Proxy:代理类
    代理类也是抽象主题类的子类,它对真实主题对象引用,调用在真实主题中实现的业务方法,在调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束
    public class Proxy implements Subject{
        //要代理的类	
    	private Subject subject=null;
    	//通过构造方法传递被代理的类的实例
    	public Proxy(Subject subject) {
    		this.subject=subject;
    	}
    	
    	//实现接口Subject中的方法
    	@Override
    	public void request() {
    		this.before(); 
    		//调用真实Subject中的方法
    		this.subject.request(); 
    		this.after();
    	}
    
    	//预处理
    	private void before() {
    		
    	}
    	
    	//善后处理
    	private void after() {
    		
    	}
    
    
    }

在实际开发过程中,代理类的实现比上述代码要复杂很多,代理模式根据其目的和实现方式不同可分为很多种。

代理模式扩展

普通代理

在网络上代理服务器设置分为透明代理和普通代理:

  • 透明代理就是用户 不用设置代理服务器地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不 用知道它存在的;
  • 普通代理则是需要用户自己设置代理服务器的IP地址,用户必须知道代理 的存在。

设计模式中的普通代理和强制代理也是类似的一种结构,普通代理就是我们要知道代理的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定。

以代练代打游戏举例说明,普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的。以代练打游戏升级的例子作为扩展,游戏玩家,自己不练级 ,场景类不直接new一个GamePlayer对象了,由GamePlayerProxy来进行模拟场景。

图12-2:普通代理类图

在这里插入图片描述
在这里插入图片描述

具体代码如下:

  • IGamePlayer:接口
    public interface IGamePlayer {
    	//登录游戏 
    	public void login(String user,String password); 
    	//杀怪,网络游戏的主要特色
    	public void killBoss();
    	//升级 
    	public void upgrade();
    }
  • GamePlayer:游戏者
    在构造函数中,传递进来一个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 + " 又升了一级!");
    	}
    
    }
  • GamePlayerProxy:代理者
    仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,在这种改造下,系统 更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁。
    public class GamePlayerProxy implements IGamePlayer{
    	private IGamePlayer gamePlayer = null;
    	
    	// 通过构造函数传递要对谁进行代练
    	public GamePlayerProxy(String name){ 
    		try { 
    			gamePlayer = new GamePlayer(this,name); 
    			} catch (Exception e) { 
    				// TODO 异常处理 } }
    		}
    	}
    	
    	//代练登录
    	@Override
    	public void login(String user, String password) {
    		this.gamePlayer.login(user, password);
    	}
    
    	//代练杀boss
    	@Override
    	public void killBoss() {
    		this.gamePlayer.killBoss();
    	}
    
    	//代练升级
    	@Override
    	public void upgrade() {
    		this.gamePlayer.upgrade();
    	}
    
    }
  • Client:场景类
    public class Client {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		//然后再定义一个代练者 
    		IGamePlayer proxy = new GamePlayerProxy("铁锤"); 
    		//开始打游戏,记下时间戳
    		System.out.println("开始时间是:2020-04-10 22:10");
    		proxy.login("zhangSan", "password"); 
    		//开始杀怪 
    		proxy.killBoss(); 
    		//升级 
    		proxy.upgrade(); 
    		//记录结束游戏时间 
    		System.out.println("结束时间是:2020-04-10 22:12");
    
    	}
    
    }

在代理中,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个 非常好的方案。

强制代理

强制代理在设计模式中比较另类,一般的思维都是通过代理找到真实 的角色,但是强制代理却是要“强制”,必须通过真实角色查找到代理角色,否则你不能访 问。不管是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。

图12-2:强制代理类图

在这里插入图片描述
在这里插入图片描述

代码如下:

  • IGamePlayer:接口
    在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除 了代理外谁都不能访问。
    public interface IGamePlayer {
    	//登录游戏 
    	public void login(String user,String password); 
    	//杀怪,网络游戏的主要特色
    	public void killBoss();
    	//升级 
    	public void upgrade();
    	//每个人都可以找一下自己的代理
    	public IGamePlayer getProxy();
    }
  • GamePlayer:强制代理的真实角色
    增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。
    public class GamePlayer implements IGamePlayer{
    	
    	private String name = "";
    	
    	//我的代理是谁 
    	private IGamePlayer proxy = null;
    
    	public GamePlayer(String _name) {
    		this.name = _name;
    	}
    
    	//找到自己的代理
    	@Override
    	public IGamePlayer getProxy() {
    		this.proxy = new GamePlayerProxy(this); 
    		return this.proxy;
    	}
    	
    	// 构造函数限制对象创建,并同时传递姓名
    	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 + " 又升了一级!");
    	}
    
    
    }
  • GamePlayerProxy:强制代理的代理类
    public class GamePlayerProxy implements IGamePlayer{
     
    	private IGamePlayer gamePlayer = null;
    	//构造函数传递用户名
    	public GamePlayerProxy(GamePlayer _gamePlayer) {
    		this.gamePlayer = _gamePlayer;
    	}
    
    	@Override
    	public void login(String user, String password) {
    		gamePlayer.login(user, password);
    	}
    
    	@Override
    	public void killBoss() {
    		gamePlayer.killBoss();
    	}
    
    	@Override
    	public void upgrade() {
    		gamePlayer.upgrade();
    	}
    
    	//代理类没有代理类,返回自己
    	@Override
    	public IGamePlayer getProxy() {
    		return this;
    	}
    
    }
  • Client:场景类
    public class Client {
    	public static void main(String[] args) {
    		// 定义一个游戏的玩家
    		IGamePlayer gamePlayer = new GamePlayer("拳拳");
    		// 获取该玩家的代理
    		IGamePlayer proxy = gamePlayer.getProxy();
    		// 开始打游戏,记下时间戳
    		System.out.println("开始时间是:2020-04-10 22:10");
    		proxy.login("quanquan", "password");
    		// 开始杀怪
    		proxy.killBoss();
    		// 升级
    		proxy.upgrade();
    		// 记录结束游戏时间
    		System.out.println("结束时间是:2020-04-10 22:12");
    	}
    
    }

强制代理的概念就是要从真实角色查找到代理角色,不允 许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本 就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。

有个性的代理

一个类可以实现多个接口,完成不同任务的整合。也就是说代理类不仅仅可以实现主题 接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义, 如图12-3所示:

图12-3:代理类的个性

在这里插入图片描述
在这里插入图片描述

增加了一个IProxy接口,其作用是计算代理的费用。

    public interface IProxy { 
      //计算费用 
      public void count(); 
    }

GamePlayerProxy类实现该接口:

    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元"); 
      } 
    
    }

同时在upgrade方法中调用该方法,完成费用结算。

动态代理

什么是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。

相对来说,自己写代理类的方式就是静态代理。面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制。

动态代理实例

还是以打游戏为例,类图修改一下以实现动态代理,如图12-4所示:

图12-4:动态代理

在这里插入图片描述
在这里插入图片描述

在类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。 我们来看程序,接口保持不变,实现类也没有变化。

  • GamePlayIH:动态代理类
    其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。
     InvocationHandler接口——动态代理是根据被代理的接口生成所有的方法, 也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,通过 InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。
    public class GamePlayIH implements InvocationHandler{
    	//被代理者
    	Class cls=null;
    	//被代理的实例
    	Object object=null;
    	//我要代理谁
    	public GamePlayIH(Object _obj) {
    		this.object=_obj;
    	}
    
    	// 调用被代理的方法
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		Object result = method.invoke(this.object, args);
    		// 如果是登录方法,则发送信息
    		if (method.getName().equalsIgnoreCase("login")) {
    			System.out.println("有人在用我的账号登录!");
    		}
    		return result;
    	}
    
    }

*Client: 场景类

    public class Client {
    
    	public static void main(String[] args) throws Throwable{
    		//定义一个玩家
    		IGamePlayer gamePlayer= new GamePlayer("辣个男人");
    		//定义一个handler
    		InvocationHandler handle=new GamePlayIH(gamePlayer);
    		//开始打游戏,记下时间戳 
    		System.out.println("开始时间是:2020-04-10 10:45");
    		//获得类的class loader
    		ClassLoader loader=gamePlayer.getClass().getClassLoader(); 
    		//动态产生一个代理者
    		IGamePlayer proxy=(IGamePlayer) Proxy.newProxyInstance(loader, new Class[] {IGamePlayer.class}, handle);
    		//登录
    		proxy.login("lagenanren", "password");
    		//开始杀怪
    		proxy.killBoss(); 
    		//升级
    		proxy.upgrade(); 
    		//记录结束游戏时间
    		System.out.println("结束时间是:2020-04-10 11:45");
    	}
    
    }

运行结果:

在这里插入图片描述
在这里插入图片描述

动态代理详解

在上面的动态代理类里,游戏登录时会通知。

这就是AOP编程。AOP编程没有使用什么新的技术,但是它对设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通 过AOP的方式切过去。

通用动态代理模型,类图如图 12-5所示:

图12-5:动态代理通用类图

在这里插入图片描述
在这里插入图片描述

动态代理实现代理的职责,业务逻辑Subject实现相关的 逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。

具体代码实现如下:

  • Subject:抽象主题
    public interface Subject {
    	//业务操作
    	public void doSomething(String str);
    }
  • RealSubject:真实主题
    public class RealSubject implements Subject{
    
    	@Override
    	public void doSomething(String str) {
    		System.out.println("do something!---->" + str);
    	}
    
    }
  • MyInvocationHandler:动态代理的Handler类
    所有通过动态代理实现的方法全部通过invoke方法调
    public class MyInvocationHandler implements InvocationHandler{
    	
    	//被代理的对象
    	private Object target = null; 
    
    	// 通过构造函数传递一个对象
    	public MyInvocationHandler(Object _obj) {
    		this.target = _obj;
    	}
    
    	@Override
    	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);
    	}
    
    }
  • 通知接口及实现
    public interface IAdvice {
    	//通知只有一个方法,执行即可 
    	public void exec();
    }
    
    public class BeforeAdvice implements IAdvice{
    
    	@Override
    	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("完事了");
    	}
    
    }

运行结果:

在这里插入图片描述
在这里插入图片描述

看看程序是怎么实现的。在DynamicProxy类 中,有这样的方法:

this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),new MyInvocationHandler(_obj));

该方法是重新生成了一个对象。注意 c.getInterfaces(),查找到该类的所有接口,然后实现接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是new MyInvocationHandler(_Obj)这个对象。于是就知道一个类的动态代理类是这样的一个类, 由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,其动态调用过程如图12-6所示。

图12-6: 动态代理调用过程示意图

在这里插入图片描述
在这里插入图片描述

以上的代码还有更进一步的扩展余地,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 Client1 {

    public static void main(String[] args) {
        //定义一个主题 
        Subject subject = new RealSubject(); 
        //定义主题的代理 
        Subject proxy = SubjectDynamicProxy.newProxyInstance(subject); 
        //代理的行为 
        proxy.doSomething("了了");
    }

}

代理模式优缺点

代理模式优点

  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

代理模式缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求 的处理速度变慢,例如保护代理。
  • 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。

作者:三分恶

来源:https://www.cnblogs.com/three-fighter/p/12677356.htmlopen in new window