Java设计模式——代理模式

1、简介

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

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

举打游戏的例子来说明:
在这里插入图片描述

定义一个接口IGamePlayer,是所有喜爱网络游戏的玩家,然后定义一个具体的实现类GamePlayer,实现每个游戏爱好者为了玩游戏要执行的功能。

(1)游戏者接口

public interface IGamePlayer {
    //登录游戏
    public void login(String user,String password);
    //杀怪, 网络游戏的主要特色
    public void killBoss();
    //升级
    public void upgrade();
}

定义了三个方法,分别是我们在网络游戏中最常用的功能:登录游戏、 杀怪和升级:

(2)实现类

public class GamePlayer implements IGamePlayer {
    private String name = "";
    //通过构造函数传递名称
    public GamePlayer(String _name){
    this.name = _name;
    }
    //打怪, 最期望的就是杀老怪
    public void killBoss() {
        System.out.println(this.name + "在打怪! ");
    }
    //进游戏之前你肯定要登录吧, 这是一个必要条件
    public void login(String user, String password) {
        System.out.println("登录名为"+user+"的用户"+this.name+"登录成功! ");
    }
    //升级, 升级有很多方法, 花钱买是一种, 做任务也是一种
    public void upgrade() {
        System.out.println(this.name + " 又升了一级! ");
    }
}

在实现类中通过构造函数传递进来玩家姓名,方便进行后期的调试工作。 我们通过一个场景类来模拟这样的游戏过程:

(3)场景类

public class Client {
    public static void main(String[] args) {
        //定义一个痴迷的玩家
        IGamePlayer player = new GamePlayer("张三");
        //开始打游戏, 记下时间戳
        System.out.println("开始时间是: 2009-8-25 10:45");
        player.login("zhangSan", "password");
        //开始杀怪
        player.killBoss();
        //升级
        player.upgrade();
        //记录结束游戏时间
        System.out.println("结束时间是: 2009-8-26 03:40");
    }
}

程序记录了游戏的开始时间和结束时间,同时也记录了在游戏过程中都需要做什么事情。

另外,游戏自己玩的太累,需要游戏代理去帮忙玩,修改下类图,如下:
在这里插入图片描述

在类图中增加了一个GamePlayerProxy类来代表游戏代练者,它也不能有作弊的方法呀,游戏代练者也是手动打怪呀,因此同样继承IGamePlayer接口, 其实现如下:

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();
    }
}

首先通过构造函数说明要代谁打怪升级,然后通过手动开始代用户打怪、 升级。 场景类Client代码也稍作改动:

public class Client {
    public static void main(String[] args) {
        //定义一个痴迷的玩家
        IGamePlayer player = new GamePlayer("张三");
        //然后再定义一个代练者
        IGamePlayer proxy = new GamePlayerProxy(player);
        //开始打游戏, 记下时间戳
        System.out.println("开始时间是: 2009-8-25 10:45");
        proxy.login("zhangSan", "password");
        //开始杀怪
        proxy.killBoss();
        //升级
        proxy.upgrade();
        //记录结束游戏时间
        System.out.println("结束时间是: 2009-8-26 03:40");
    }
}

这就是简单代理模式说明,下面看一看代理模式的官方点的描述:

2、推荐实现案例

代理模式(Proxy Pattern)是一个使用率非常高的模式, 其定义是,为其他对象提供一种代理以控制对这个对象的访问。

代理模式的通用类图:
在这里插入图片描述
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。在一些著名开源软件中也经常见到它的身影,如Struts2的Form元素映射就采用了代理模式(准确地说是动态代理模式)。我们先看一下类图中的三个角色的定义:

(1)Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义, 无特殊要求。

(2)RealSubject具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。

(3)Proxy代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

首先来看Subject抽象主题类的通用源码:
(1)抽象主题类

public interface Subject {
    //定义一个方法
    public void request();
}

(2)真实主题类
在接口中我们定义了一个方法request来作为方法的代表, RealSubject对它进行实现:

public class RealSubject implements Subject {
    //实现方法
    public void request() {
        //业务逻辑处理
    }
}

RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上:

(3)代理类

public class Proxy implements Subject {
    //要代理哪个实现类
    private Subject subject = null;
    //默认被代理者
    public Proxy(){
        this.subject = new Proxy();
    }
    //通过构造函数传递代理者
    public Proxy(Object...objects ){
    }
    //实现接口中定义的方法
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }
    //预处理
    private void before(){
        //do something
    }
    //善后处理
    private void after(){
        //do something
    }
}

看到这里,大家别惊讶,为什么会出现before和after方法,继续看下去,这是一个“引子”,能够引出一个崭新的编程模式。

一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的。当然,最简单的情况就是一个主题类和一个代理类,这是最简洁的代理模式。在通常情况下,一个接口只需要一个代理类就可以了,具体代理哪个实现类由高层模块来决定,也就是在代理类的构造函数中传递被代理者,例如我们可以在代理类Proxy中增加如下所示的构造函数:

public Proxy(Subject _subject){
    this.subject = _subject;
}

你要代理谁就产生该代理的实例,然后把被代理者传递进来,该模式在实际的项目应用中比较广泛。

3、优缺点

3.1 优点

  • 职责清晰
    真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

  • 高拓展性
    具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。

  • 智能化
    在我们以上的讲解中还没有体现出来,不过在我们以下的动态代理章节中你就会看到代理的智能化有兴趣的读者也可以看看Struts是如何把表单元素映射到对象上的。

3.2 缺点

4、应用场景

我相信第一次接触到代理模式的读者肯定很郁闷,为什么要用代理呀? 想想现实世界吧,打官司为什么要找个律师?因为你不想参与中间过程的是是非非,只要完成自己的答辩就成,其他的比如事前调查、事后追查都由律师来搞定,这就是为了减轻你的负担。代理模式的使用场景非常多, 大家可以看看Spring AOP,这是一个非常典型的动态代理。

5、注意事项

6、拓展

6.1 普通代理

在网络上代理服务器设置分为透明代理和普通代理,是什么意思呢?透明代理就是用户不用设置代理服务器地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不用知道它存在的;普通代理则是需要用户自己设置代理服务器的IP地址,用户必须知道代理的存在。我们设计模式中的普通代理和强制代理也是类似的一种结构,普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的,这样的解释还是比较复杂,我们还是用实例来讲解。

首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的。我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定自己不练级了,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GamePlayerProxy来进行模拟场景,类图修改如图:
在这里插入图片描述

改动很小,仅仅修改了两个实现类的构造函数,GamePlayer的构造函数增加了_gamePlayer参数, 而代理角色则只要传入代理者名字即可, 而不需要说是替哪个对象做代理。

(1)GamePlayer类(真实类)如代码:

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;
        }
    }
    //打怪, 最期望的就是杀老怪
    public void killBoss() {
        System.out.println(this.name + "在打怪! ");
    }
    //进游戏之前你肯定要登录吧, 这是一个必要条件
    public void login(String user, String password) {
        System.out.println("登录名为"+user + "的用户" + this.name + "登录成功! ")
    }
    //升级, 升级有很多方法, 花钱买是一种, 做任务也是一种
    public void upgrade() {
        System.out.println(this.name + " 又升了一级! ");
    }
}

在构造函数中,传递进来一个IGamePlayer对象,检查谁能创建真实的角色,当然还可以有其他的限制,比如类名必须为Proxy类等,读者可以根据实际情况进行扩展。GamePlayerProxy如代码:

(2)普通代理的代理者:

public class GamePlayerProxy implements IGamePlayer {
    private IGamePlayer gamePlayer = null;
    //通过构造函数传递要对谁进行代练
    public GamePlayerProxy(String name){
        try {
            gamePlayer = new GamePlayer(this,name);
        } catch (Exception e) {
            // TODO 异常处理
        }
    }
    //代练杀怪
    public void killBoss() {
        this.gamePlayer.killBoss();
    }
    //代练登录
    public void login(String user, String password) {
        this.gamePlayer.login(user, password);
    }
    //代练升级
    public void upgrade() {
        this.gamePlayer.upgrade();
    }
}

仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,在这种改造下,系统更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁。 同时场景类也稍作改动,如代码:

(3)普通代理的场景类

public class Client {
    public static void main(String[] args) {
        //然后再定义一个代练者
        IGamePlayer proxy = new GamePlayerProxy("张三");
        //开始打游戏, 记下时间戳
        System.out.println("开始时间是: 2009-8-25 10:45");
        proxy.login("zhangSan", "password");
        //开始杀怪
        proxy.killBoss();
        //升级
        proxy.upgrade();
        //记录结束游戏时间
        System.out.println("结束时间是: 2009-8-26 03:40");
    }
}

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

注意:
普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题类是可被重用的和可维护的,使用技术约束的方式对系统维护是一种非常不利的因素。

6.2 强制代理

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

在接口上增加了一个getProxy方法,真实角色GamePlayer可以指定一个自己的代理,除了代理外谁都不能访问。

(1)强制代理的接口类

public interface IGamePlayer {
    //登录游戏
    public void login(String user,String password);
    //杀怪, 这是网络游戏的主要特色
    public void killBoss();
    //升级
    public void upgrade();
    //每个人都可以找一下自己的代理
    public IGamePlayer getProxy();
}

仅仅增加了一个getProxy方法,指定要访问自己必须通过哪个代理,实现类也要做适当的修改, 先看真实角色GamePlayer。

(2)强制代理的真实角色

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;
    }
}
}

增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问。我们再来看代理角色:

(3)强制代理的代理类

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;
    }
}

代理角色也可以再次被代理,这里我们就没有继续延伸下去了,查找代理的方法就返回自己的实例。

(4)场景类

public class Client {
    public static void main(String[] args) {
        //定义一个游戏的角色
        IGamePlayer player = new GamePlayer("张三");
        //获得指定的代理
        IGamePlayer proxy = player.getProxy();
        //开始打游戏, 记下时间戳
        System.out.println("开始时间是: 2009-8-25 10:45");
        proxy.login("zhangSan", "password");
        //开始杀怪
        proxy.killBoss();
        //升级
        proxy.upgrade();
        //记录结束游戏时间
        System.out.println("结束时间是: 2009-8-26 03:40");
    }
}

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

6.3 代理类的个性

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

增加了一个IProxy接口,其作用是计算代理的费用。我们先来看IProxy接口,如代码:

(1)代理类的接口

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

(2)代理类

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方法中调用该方法,完成费用结算,其他的类都没有任何改动。

代理类不仅仅是可以有自己的运算方法,通常的情况下代理的职责并不一定单一,它可以组合其他的真实角色,也可以实现自己的职责,比如计算费用。代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系,可以自行扩展。

6.4 动态代理

什么是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。现在有一个非常流行的名称叫做面向横切面编程,也就是AOP(AspectOrientedProgramming),其核心就是采用了动态代理机制,既然这么重要,我们就来看看动态代理是如何实现的,还是以打游戏为例,类图修改一下以实现动态代理:
在这里插入图片描述

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

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接管实际的处理任务。我们接下来看看场景类,如代码:

(2)动态代理场景类

public class Client {
    public static void main(String[] args) throws Throwable {
        //定义一个痴迷的玩家
        IGamePlayer player = new GamePlayer("张三");
        //定义一个handler
        InvocationHandler handler = new GamePlayIH(player);
        //开始打游戏, 记下时间戳
        System.out.println("开始时间是: 2009-8-25 10:45");
        //获得类的class loader
        ClassLoader cl = player.getClass().getClassLoader();
        //动态产生一个代理者
        IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class}, handler);
        //登录
        proxy.login("zhangSan", "password");
        //开始杀怪
        proxy.killBoss();
        //升级
        proxy.upgrade();
        //记录结束游戏时间
        System.out.println("结束时间是: 2009-8-26 03:40");
    }
}

我们还是让代练者帮我们打游戏,但是我们既没有创建代理类,也没有实现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接口:
(1)抽象主题

public interface Subject {
    //业务操作
    public void doSomething(String str);
}

其中的doSomething是一种标识方法,可以有多个逻辑处理方法,实现类如代码清单:
(2)真实主题

public class RealSubject implements Subject {
    //业务操作
    public void doSomething(String str) {
        System.out.println("do something!---->" + str);
    }
}

重点是我们的MyInvocationHandler,如代码清单:

(3)动态代理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方法调用。 DynamicProxy代码如代码清单:

(4)动态代理类

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术语,如在什么地方(连接点)执行什么行为(通知)。我们在这里实现了一个简单的横切面编程,我们来看通知Advice,也就是我们要切入的类,接口和实现如代码清单

(5)通知接口及实现

public interface IAdvice {
    //通知只有一个方法, 执行即可
    public void exec();
    }
    public class BeforeAdvice implements IAdvice{
        public void exec(){
            System.out.println("我是前置通知, 我被执行了! ");
        }
    }
}

(6)动态代理的场景类

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()这句话,这是非常有意思的一句话,是说查找到该类的所有接口,然后实现接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是newMyInvocationHandler(_Obj)这个对象。于是我们知道一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,其动态调用过程如图:
在这里插入图片描述
以上的代码还有更进一步的扩展余地,注意看DynamicProxy类,它
是一个通用类,不具有业务意义,如果我们再产生一个实现类是不是就很有意义了呢?

(1)具体业务的动态代理:

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

如此扩展以后, 高层模块对代理的访问会更加简单。

(2) 场景类

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

是不是更加简单了?这样与静态代理还有什么区别?都是需要实现一个代理类,有区别,注意看父类,动态代理的主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为。

注意:
要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下前面的分析吧。当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。

再次说明,以上的动态代理是一个通用代理框架。如果你想设计自己的AOP框架,完全可以在此基础上扩展,我们设计的是一个通用代理,只要有一个接口,一个实现类,就可以使用该代理,完成代理的所有功效。

7、最佳实践

代理模式应用得非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理,稍不留意就用到代理模式。可能该模式是大家接触最多的模式,而且有了AOP大家写代理就更加简单了,有类似SpringAOP和AspectJ这样非常优秀的工具,拿来主义即可!不过,大家可以看看源代码,特别是调试时,只要看到类似$Proxy0这样的结构,你就应该知道这是一个动态代理了。

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页