zoukankan      html  css  js  c++  java
  • 每天学习一个设计模式(七):结构型之代理模式

    目录

    一、基本概念

    二、通俗解释

    三、代理模式的分类

    1.普通代理

    2.强制代理

    代理是有个性的

    3.动态代理


    一、基本概念

    代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义如下:Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

    二、通俗解释

    PROXY代理模式:跟MM在网上聊天,一开头总是“hi,你好”,“你从哪儿来呀?”“你多大了?”“身高多少呀?”这些话,真烦人,写个程序做为我的Proxy吧,凡是接收到这些话都设置好了自己的回答,接收到其他的话时再通知我回答,怎么样,酷吧。 代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。

    网游案例

    2017年,感觉很无聊,于是就玩了一段时间的网络游戏,游戏名就不说了,反正就是打怪、升级、砍人、被人砍,然后继续打怪、升级、打怪、升级……我花了两个月的时间升到80级,已经很有成就感了,但是还会被人杀死,高手到处都是,GM(Game Master,游戏管理员)也不管,对于咱这种非RMB玩家基本上都是懒得搭理。在这段时间我是体会到网络游戏的乐与苦,参与家族(工会)攻城,胜利后那叫一个乐呀,感觉自己真是一个“狂暴战士”,无往不胜!那苦是什么呢?就是升级,为了升一级,就要到处杀怪,做任务,那个游戏还很变态,外挂管得很严,基本上出个外挂,没两天就开始封账号,不敢用,升级基本上都要靠自己手打,累呀!我曾经的记录是连着打了23个小时,睡觉在梦中还和大BOSS在PK。有这样一段经历还是很有意思的,作为架构师是不是可以把这段经历通过架构的方式记录下来呢?当然可以了,我们把这段打游戏的过程系统化,非常简单的一个过程,如图所示。

            

    太简单了,定义一个接口IGamePlayer,是所有喜爱网络游戏的玩家,然后定义一个具体的实现类GamePlayer,实现每个游戏爱好者为了玩游戏要执行的功能。代码也非常简单,我们先来看IGamePlayer。

    游戏者接口

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

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

    游戏玩家类

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

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

    客户端类

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

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

    开始时间是: 2020-05-26 10:06

    登录名为zhangSan的用户张三登录成功!

    张三在打怪!

    张三又升了一级!

    结束时间是: 2020-05-26 10:30

    运行结果也是我们想要的,记录我这段时间的网游生涯。心理学家告诉我们,人类对于苦难的记忆比对喜悦的记忆要深刻,但是人类对于喜悦是“趋利”性的,每个人都想Happy,都不想让苦难靠近,要想获得幸福,苦难也是在所难免的,我们的网游生涯也是如此。游戏打时间长了,腰酸背痛、眼睛干涩、手臂酸麻,等等,也就是网络成瘾综合症都出来了。其结果就类似吃了那个“一日丧命散”,“筋脉逆流,胡思乱想,而致走火入魔”。那怎么办呢?我们想玩游戏,但又不想碰触到游戏中的烦恼,如何解决呢?有办法,现在游戏代练的公司非常多,我把自己的账号交给代练人员,由他们去帮我升级,去打怪,非常好的想法,我们来修改一下类图,如图所示。

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

    代练者类

    public class GamePlayerProxy implements IGamePlayer {
        private IGamePlayer gamePlayer = null;
    
        /**
         * 通过构造函数传递要对谁进行代练
         * @param gamePlayer
         */
        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();
        }
    }

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

    改进后的客户端类

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

    运行结果也完全相同,还是张三这个用户在打怪,运行结果如下:

    开始时间是: 2020-05-26 10:36

    登录名为zhangSan的用户张三登录成功!

    张三在打怪!

    张三又升了一级!

    结束时间是: 2020-05-26 10:40

    是的,没有任何改变,但是你有没有发觉,你的游戏已经在升级,有人在帮你干活了!终于升级到120级,基本上在本服务区无敌,除了GM外,这个你可惹不起!这就是代理模式。

    三、代理模式的分类

    代理模式的分类很多,根据代理方式分为静态代理和动态代理,根据应用场合,有远程代理、虚拟代理和安全代理等等。

    根据秦小波的《设计模式之禅(第2版)》将代理模式扩展为三种:普通代理、强制代理、动态代理。

    1.普通代理

    在网络上代理服务器设置分为透明代理和普通代理,是什么意思呢?透明代理就是用户不用设置代理服务器地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不用知道它存在的;普通代理则是需要用户自己设置代理服务器的IP地址,用户必须知道代理的存在。我们设计模式中的普通代理和强制代理也是类似的一种结构,普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的,这样的解释还是比较复杂,我们还是用实例来讲解。首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的。我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定自己不练级了,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GamePlayerProxy来进行模拟场景,类图修改如图所示。

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

    普通代理的玩家类

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

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

    普通代理的代理者类

    public class GamePlayerProxy implements IGamePlayer {
        private IGamePlayer gamePlayer = null;
    
        /**
         * 通过构造函数传递要对谁进行代练
         *
         * @param name
         */
        public GamePlayerProxy(String name) {
            try {
                gamePlayer = new GamePlayer(this, name);
            } catch (Exception e) {
                //异常处理
            }
        }
    
        @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();
        }
    }

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

    普通代理的客户端类

    public class Client {
        public static void main(String[] args) {
            //定义一个代练者
            IGamePlayer proxy = new GamePlayerProxy("张三");
            //开始打游戏,记下开始时间
            System.out.println("开始时间是: 2020-05-26 10:54");
            proxy.login("zhangsan", "password");
            //开始杀怪
            proxy.killBoss();
            //升级
            proxy.upgrade();
            //记录结束时间
            System.out.println("结束时间是: 2020-05-26 11:10");
        }
    }

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

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

    2.强制代理

    强制代理在设计模式中比较另类,为什么这么说呢?一般的思维都是通过代理找到真实的角色,但是强制代理却是要“强制”,你必须通过真实角色查找到代理角色,否则你不能访问。甭管你是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色。这么说吧,高层模块new了一个真实角色的对象,返回的却是代理角色,这就好比是你和一个明星比较熟,相互认识,有件事情你需要向她确认一下,于是你就直接拨通了明星的电话:

    “喂,沙比呀,我要见一下×××导演,你帮下忙了!”

    “不行呀衰哥,我这几天很忙呀,你找我的经纪人吧……”

    郁闷了吧,你是想直接绕过她的代理,谁知道返回的还是她的代理,这就是强制代理,你可以不用知道代理存在,但是你的所作所为还是需要代理为你提供。我们把上面的例子稍作修改就可以完成,如图所示。

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

    强制代理的接口类

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

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

    强制代理的真实角色类

    public class GamePlayer implements IGamePlayer {
        private String name = "";
        /**
         * 我代理的是谁
         */
        private IGamePlayer proxy = null;
    
        /**
         * 通过构造函数传递名称
         *
         * @param name
         */
        public GamePlayer(String name) {
            this.name = name;
        }
    
        /**
         * 打怪,最期望的就是杀老怪
         */
        @Override
        public void killBoss() {
            if (this.isProxy()) {
                System.out.println(this.name + "在打怪!");
            } else {
                System.out.println("请使用指定的代理访问");
            }
        }
    
        /**
         * 进有戏之前你肯定要登录吧,这是一个必要条件
         *
         * @param user
         * @param password
         */
        @Override
        public void login(String user, String password) {
            if (this.isProxy()) {
                System.out.println("登录名为" + user + "的用户" + this.name + "登录成功!");
            } else {
                System.out.println("请使用指定的代理访问");
            }
        }
    
        /**
         * 升级,升级有很多方法,花钱买是一种,做任务也是一种
         */
        @Override
        public void upgrade() {
            if (this.isProxy()) {
                System.out.println(this.name + " 又升了一级!");
            } else {
                System.out.println("请使用指定的代理访问");
            }
        }
    
        /**
         * 找到自己的代理
         *
         * @return
         */
        @Override
        public IGamePlayer getProxy() {
            this.proxy = new GamePlayerProxy(this.name);
            return this.proxy;
        }
    
        /**
         * 检验是否是代理访问
         *
         * @return
         */
        private boolean isProxy() {
            return this.proxy != null;
        }
    }

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

    强制代理的代理类

    public class GamePlayerProxy implements IGamePlayer {
        private IGamePlayer gamePlayer = null;
    
        /**
         * 通过构造函数传递要对谁进行代练
         *
         * @param gamePlayer
         */
        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();
        }
    
        /**
         * 代理的代理暂时还没有,就是自己
         *
         * @return
         */
        @Override
        public IGamePlayer getProxy() {
            return this;
        }
    }

    代理角色也可以再次被代理,这里我们就没有继续延伸下去了,查找代理的方法就返回自己的实例。代码都写完毕了,我们先按照常规的思路来运行一下,直接new一个真实角色。

    直接访问真实角色

    public class Client {
        public static void main(String[] args) {
            //定义一个游戏角色
            IGamePlayer player = new GamePlayer("张三");
            //开始打游戏,记下开始时间
            System.out.println("开始时间是: 2020-05-26 11:14");
            player.login("zhangsan", "password");
            //开始杀怪
            player.killBoss();
            //升级
            player.upgrade();
            //记录结束时间
            System.out.println("结束时间是: 2020-05-26 11:28");
        }
    }

    想想看能运行吗?运行结果如下所示:

    开始时间是: 2020-05-26 11:14

    请使用指定的代理访问

    请使用指定的代理访问

    请使用指定的代理访问

    结束时间是: 2020-05-26 11:28

    它要求你必须通过代理来访问,你想要直接访问它,门儿都没有,好,你要我通过代理来访问,那就生产一个代理。

    直接访问代理类

    public class Client {
        public static void main(String[] args) {
            //定义一个游戏角色
            IGamePlayer player = new GamePlayer("张三");
            //然后在定义一个代练者
            IGamePlayer proxy = new GamePlayerProxy(player);
            //开始打游戏,记下开始时间
            System.out.println("开始时间是: 2020-05-26 11:14");
            proxy.login("zhangsan", "password");
            //开始杀怪
            proxy.killBoss();
            //升级
            proxy.upgrade();
            //记录结束时间
            System.out.println("结束时间是: 2020-05-26 11:28");
        }
    }

    这次能访问吗?还是不行,结果如下所示:

    开始时间是: 2020-05-26 11:14

    请使用指定的代理访问

    请使用指定的代理访问

    请使用指定的代理访问

    结束时间是: 2020-05-26 11:28

    还是不能访问,为什么呢?它不是真实角色指定的对象,这个代理对象是你自己new出来的,当然真实对象不认了,这就好比是那个明星,人家已经告诉你去找她的代理人了,你随便找个代理人能成吗?你必须去找她指定的代理才成!我们修改一下客户端类。

    强制代理的客户端类

    public class Client {
        public static void main(String[] args) {
            //定义一个游戏角色
            IGamePlayer player = new GamePlayer("张三");
            //然后在定义一个代练者
            IGamePlayer proxy = player.getProxy();
            //开始打游戏,记下开始时间
            System.out.println("开始时间是: 2020-05-26 11:14");
            proxy.login("zhangsan", "password");
            //开始杀怪
            proxy.killBoss();
            //升级
            proxy.upgrade();
            //记录结束时间
            System.out.println("结束时间是: 2020-05-26 11:28");
        }
    }

    运行结果如下:

    开始时间是: 2020-05-26 11:14

    登录名为zhangSan的用户张三登录成功!

    张三在打怪!

    张三又升了一级!

    结束时间是: 2020-05-26 11:28

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

    代理是有个性的

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

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

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

    仅仅一个方法,非常简单,看GamePlayerProxy带来的变化。

    public class GamePlayerProxy implements IGamePlayer,IProxy {
        private IGamePlayer gamePlayer = null;
    
        /**
         * 通过构造函数传递要对谁进行代练
         *
         * @param gamePlayer
         */
        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();
        }
    
        /**
         * 代理的代理暂时还没有,就是自己
         *
         * @return
         */
        @Override
        public IGamePlayer getProxy() {
            return this;
        }
    
        @Override
        public void count() {
            System.out.println("升级总费用是:150元");
        }
    }

    实现了IProxy接口,同时在upgrade方法中调用该方法,完成费用结算,其他的类都没有任何改动,运行结果如下:

    开始时间是: 2020-05-26 11:14

    登录名为zhangSan的用户张三登录成功!

    张三在打怪!

    张三又升了一级!

    升级总费用是:150元

    结束时间是: 2020-05-26 11:28

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

    3.动态代理

    放在最后讲的一般都是压轴大戏,动态代理就是如此,上面的章节都是一个引子,动态代理才是重头戏。什么是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。本章节的核心部分就在动态代理上,现在有一个非常流行的名称叫做面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制,既然这么重要,我们就来看看动态代理是如何实现的,还是以打游戏为例,类图修改一下以实现动态代理,如图所示。

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

    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 {
            return method.invoke(this.obj, args);
        }
    }

    其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。我们来详细讲解一下InvocationHandler接口,动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,那各位读者想想看,动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。我们接下来看看客户端类。

    很奇怪是吗?不要着急,继续看下去。其运行结果如下:

    开始时间是: 2020-05-26 11:14
    登录名为zhangsan的用户张三登录成功!
    张三在打怪!
    张三 又升了一级!
    结束时间是: 2020-05-26 11:28

    我们还是让代练者帮我们打游戏,但是我们既没有创建代理类,也没有实现IGamePlayer接口,这就是动态代理。别急,动态代理可不仅仅就这么多内容,还有更重要的,如果想让游戏登录后发一个信息给我们,防止账号被人盗用嘛,该怎么处理?直接修改被代理类GamePlayer?这不是一个好办法,好办法如代码清单所示。

    修正后的动态代理

    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 ("login".equalsIgnoreCase(method.getName())) {
                System.out.println("有人在用我的账号登录");
            }
            return result;
        }
    }

    只要在代理中增加一个判断就可以决定是否要发送信息,运行结果如下:

    开始时间是: 2020-05-26 11:14
    登录名为zhangsan的用户张三登录成功!
    有人在用我的账号登录
    张三在打怪!
    张三 又升了一级!
    结束时间是: 2020-05-26 11:28

    太棒了!有人用我的账号就发送一个信息,然后看看自己的账号是不是被人盗了,非常好的方法,这就是AOP编程。AOP编程没有使用什么新的技术,但是它对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。既然动态代理是如此诱人,我们来看看通用动态代理模型,类图如图所示。

    .

    很简单,两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。我们先来看Subject接口。

    抽象主题

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

    其中的doSomething是一种标识方法,可以有多个逻辑处理方法。

    真实主题

    public class RealSubject implements Subject {
        @Override
        public void doSomething(String str) {
            System.out.println("do something!--->" + str);
        }
    }

    重点是我们的MyInvocationHandler。

    public class MyInvocationHandler implements InvocationHandler {
        /**
         * 被代理的对象
         */
        private Object target = null;
    
        /**
         * 通过构造函数传递一个对象
         *
         * @param obj
         */
        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);
        }
    }

    非常简单,所有通过动态代理实现的方法全部通过invoke方法调用。DynamicProxy动态代理类。

    public class DynamicProxy<T> {
        public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler){
            //寻找JoinPoint连接点,AOP框架使用元数据定义
            if (true){
                (new BeforeAdvice()).exec();
            }
            //执行目标,并返回结果
            return (T) Proxy.newProxyInstance(loader,interfaces,handler);
        }
    }

    在这里插入了较多的AOP术语,如在什么地方(连接点)执行什么行为(通知)。我们在这里实现了一个简单的横切面编程,有经验的读者可以看看AOP的配置文件就会明白这段代码的意义了。我们来看通知Advice,也就是我们要切入的类,通知接口及实现如下:

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

    最后就是看我们怎么调用了。

    动态代理的客户端类

    运行结果如下所示:

    我是前置通知,我被执行了!

    do something!---->Finish

    好,所有的程序都看完了,我们回过头来看看程序是怎么实现的。在DynamicProxy类中,我们有这样的方法:this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),newMyInvocationHandler(obj));

    该方法是重新生成了一个对象,为什么要重新生成?你要使用代理呀,注意c.getInterfaces()这句话,这是非常有意思的一句话,是说查找到该类的所有接口,然后实现接口的所有方法。当然了,方法都是空的,由谁具体负责接管呢?是newMyInvocationHandler(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);
        }
    }

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

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

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

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

    代理模式应用得非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理,稍不留意就用到代理模式。可能该模式是大家接触最多的模式,而且有了AOP大家写代理就更加简单了,有类似Spring AOP和AspectJ这样非常优秀的工具,拿来主义即可!不过,大家可以看看源代码,特别是调试时,只要看到类似$Proxy0这样的结构,你就应该知道这是一个动态代理了。友情提醒,在学习AOP框架时,弄清楚几个名词就成:切面(Aspect)、切入点(JoinPoint)、通知(Advice)、织入(Weave)就足够了,理解了这几个名词,应用时你就可以游刃有余了!


    参考秦小波的《设计模式之禅(第2版)》

  • 相关阅读:
    bug_ _ 常见的bug1
    键盘-App监听软键盘按键的三种方式
    listview--记录ListView滚动停止位置与设置显示位置
    android shape的使用
    转-android图片降低图片大小保持图片清晰的方法
    转-Android SHA1与Package获取方式
    Android listview中使用checkbox
    保存恢复临时信-Android 中使用onSaveInstanceState和onRestoreInstanceState
    转-Android客户端和服务端如何使用Token和Session
    转-Activity中使用orientation属性讲解及需注意的问题
  • 原文地址:https://www.cnblogs.com/aohongzhu/p/15174403.html
Copyright © 2011-2022 走看看