结构篇-代理模式

Posted zhixuChen333

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结构篇-代理模式相关的知识,希望对你有一定的参考价值。

文章目录


前言

代理模式(Proxy),顾名思义,有代表打理的意思。某些情况下,当客户端不能或不适合直接访问目标业务对象时,业务对象可以通过代理把自己的业务托管起来,使客户端间接地通过代理进行业务访问。如此不但能方便用户使用,还能对客户端的访问进行一定的控制。简单来说,就是代理方以业务对象的名义,代理了它的业务。


提示:以下是本篇文章正文内容,下面案例可供参考

一、4S店

在我们的社会活动中存在着各种各样的代理,例如销售代理商,他们受商品制造商委托负责代理商品的销售业务,而购买方(如最终消费者)则不必与制造商发生关联,也不用关心商品的具体制造过程,而是直接找代理商购买产品。

顾客通常不会找汽车制造商直接购买汽车,而是通过4S店购买。介于顾客与制造商之间,4S店对汽车制造商生产的整车与零配件提供销售代理服务,并且在制造商原本职能的基础之上增加了一些额外的附加服务,如汽车上牌、注册、保养、维修等,使顾客与汽车制造商彻底脱离关系。除此之外,代理模式的示例还有明星经纪人对明星推广业务的代理;律师对原告或被告官司的代理;旅游团对门票、机票业务的代理等,不胜枚举。

二、访问互联网

现代社会中,网络已经渗透到人们工作和生活的方方面面,为了满足各种需求,不管是公司还是家庭,网络的组建工作都必不可少。根据网络环境的不同,适当地使用各种网络设备十分重要,例如我们常见的家用路由器,其最重要的一个功能就是代理上网业务,使其下面所有终端设备都能够连入互联网。

首先我们得去网络服务提供商(ISP)申请互联网(Internet)宽带业务,然后通过光纤入户并拿到一个调制解调器(Modem),也就是我们俗称的“猫”(以下简称为猫),它负责在模拟信号(或者光信号)与数字信号之间做调制转换(类似于适配器)。接下来连接的就是我们的主角——路由器(Router)了,它负责代理互联网服务。最后,我们每天使用的一些终端设备,例如笔记本电脑、台式机、手机、电视机等,不管通过Wi-Fi还是网线,都能通过路由器代理成功上网。基于此结构,我们尝试以代码来实现。

1.互联网访问接口

public interface Internet 
    void httpAccess(String url);

2.“猫”

public class Modem implements Internet

    public Modem(String password) throws Exception 
        if(!"123456".equals(password)) 
            throw new Exception("拨号失败,请重试!");
        
        System.out.println("拨号上网......连接成功");
    

    @Override
    public void httpAccess(String url) 
        System.out.println("正在访问:" + url);
    

说明:

  1. 调制解调器(猫)实现了互联网访问接口,并在构造方法中进行拨号上网的密码校验,校验通过后用户即可通过调用互联网访问实现方法httpAccess()上网了。此方法来者不拒,接受用户的一切访问。

三、互联网代理

虽然“猫”允许用户直接访问互联网,但用户每次上网都不得不进行拨号操作,这确实不太方便。此外,“猫”要对大量的终端上网设备进行资源分配与管理,难免力不从心。例如,孩子学习时总是偷偷上网看电影或玩游戏,只依靠家长是很难得到有效控制的。再如,我们上网时会遭遇一些高危网站的攻击,严重威胁到我们的网络安全,所以我们有必要采取一些技术手段来屏蔽终端设备对这些有害网站的访问,这些事还是得交给代理去负责,例如建立黑名单机制。

1. 路由器

要在用户与互联网之间建立黑名单机制并禁止终端设备对有害网站的访问,我们就得把终端设备(客户端)与“猫”的连接隔离开,并在它们之间加上路由器进行代理管控。当终端设备请求访问互联网时,我们就将其传入的地址与黑名单进行比对,如果该地址存在于黑名单中则禁止访问,反之则通过校验并转交给“猫”以连接互联网。这个逻辑非常清晰,下面我们用路由器来实现。

public class RouterProxy implements Internet 

    private Internet     modem;//被代理的对象
    private List<String> blackList = Arrays.asList("电影", "游戏", "小说", "音乐");

    public RouterProxy() throws Exception
        this.modem = new Modem("123456");//实例化被代理类
    

    //实现互联网访问接口
    @Override
    public void httpAccess(String url) 
        //遍历黑名单
        for(String keyword : blackList)
            if(url.contains(keyword))
                System.out.println("禁止访问:" + url);
                return;
            
        
        modem.httpAccess(url);
    

说明:

  1. 路由器与“猫”一样实现了互联网接口,并于构造方法中主动实例化了“猫”,作为被代理的目标业务(互联网业务)类。
  2. 重点在于互联网访问实现方法中,我们对提前设定好的黑名单进行遍历,如果访问地址中带有黑名单中的敏感字眼就禁止访问并直接退出,如果遍历结束则代表没有发现任何威胁,此时就可以假设访问地址是相对安全的。
  3. 当访问地址成功通过安全校验后,代码路由器移交控制权,将请求转发给“猫”进行互联网访问。可以看到,其实路由器本质上并不具备上网功能,而只是充当代理角色,对访问进行监管、控制与转发。

2.客户端类

public class Client 
    public static void main(String[] args) throws Exception 
        Internet proxy = new RouterProxy();
        proxy.httpAccess("http://www.电影.com");
        proxy.httpAccess("http://www.游戏.com");
        proxy.httpAccess("http://www.学习.com/java");
        proxy.httpAccess("http://www.工作.com");
    

输出结果:
拨号上网......连接成功
禁止访问:http://www.电影.com
禁止访问:http://www.游戏.com
正在访问:http://www.学习.com/java
正在访问:http://www.工作.com

说明:

  1. 客户端(终端设备)一开始创建的并不是“猫”,而是实例化路由器来连接互联网。简单来讲,就是用户只需要知道连接路由器便可以上网了,至于“猫”是什么,用户完全可以无视。

四、万能的动态代理

通过代码实践,相信读者已经充分理解代理模式了,这也是最简单、常用的一种代理模式。除此之外,还有一种特殊的代理模式叫作“动态代理”,其实例化过程是动态完成的,也就是说我们不需要专门针对某个接口去编写代码实现一个代理类,而是在接口运行时动态生成。

1.局域网访问接口

继续我们之前的实例,现在假设有这样一种场景,当网络中的终端设备越来越多(例如组建公司网络)时,网络接口逐渐被占满,此时路由器就有点力不从心、不堪负重。这就需要我们进行网络升级,加装交换机来连接更多的终端设备。由于交换机主要负责内网的通信服务,因此现在我们将视角切换到局域网,首先定义局域网访问接口Intranet

public interface Intranet 
     void fileAccess(String path);

说明:

  1. 与之前的互联网访问接口Internet定义的httpAccess不同,局域网访问接口Intranet定义了文件访问标准(协议)fileAccess,并以文件的绝对地址作为参数。

2.交换机

public class Switch implements Intranet

    @Override
    public void fileAccess(String path) 
        System.out.println("访问内网" + path);
    

说明:

  1. 交换机Switch实现了局域网访问接口Intranet,此时终端设备间的互访也就顺利实现了,如一台计算机请求从另一台内网计算机上复制共享文件。但交换机还不具备任何代理功能,不要着急,接下来就需要动态代理了。

3.动态代理

随着终端设备数量的增多,内网安全防范措施也得跟着加强。若要对终端设备之间的互访进行管控,我们就不得不再编写一个局域网接口的代理SwitchProxy,并加上之前的黑名单过滤逻辑。这虽然看似简单,但问题是,不管是代理互联网业务还是代理局域网业务,都是基于同样的一份黑名单对访问地址进行校验,如果每个代理都加上这一逻辑,显然是冗余的,将其抽离出来势在必行。

随着终端设备数量的增多,内网安全防范措施也得跟着加强。若要对终端设备之间的互访进行管控,我们就不得不再编写一个局域网接口的代理SwitchProxy,并加上之前的黑名单过滤逻辑。这虽然看似简单,但问题是,不管是代理互联网业务还是代理局域网业务,都是基于同样的一份黑名单对访问地址进行校验,如果每个代理都加上这一逻辑,显然是冗余的,将其抽离出来势在必行。

public class BlackListFilter implements InvocationHandler 

    private List<String> blackList = Arrays.asList("电影", "游戏", "小说", "音乐");

    //被代理的真实对象,如”猫”、交换机等
    private Object origin;

    public BlackListFilter(Object origin) 
        this.origin = origin;
        System.out.println("开启黑名单过滤功能......");
    

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        //切入“方法面”之前的过滤器逻辑
        String arg = args[0].toString();
        //遍历黑名单
        for (String keyword : blackList) 
            if (arg.contains(keyword)) 
                System.out.println("禁止访问:" + arg);
                return null;
            
        
        //调用被代理对象方法
        System.out.println("校验通过,转向实际业务");
        return method.invoke(origin, args);
    

说明:

  1. 黑名单过滤器的功能代码不再与任何业务接口有瓜葛了,而且实现了JDK反射包中提供的InvocationHandler(动态调用处理器)接口,这个接口定义了动态反射调用的标准,这意味着黑名单过滤器可以代理任意类的任意方法,这就使万能代理成为可能。

4.改进后的路由代理

们已经将黑名单机制的相关逻辑抽离出来了,并且加上了动态代理生成的功能,那么我们之前实现的路由器代理就要进行重构,删除其中的黑名单过滤功能代码,只保留自动拨号功能。

public class RouterProxy implements Internet 

    private Internet     modem;//被代理的对象

    public RouterProxy() throws Exception
        this.modem = new Modem("123456");//实例化被代理类
    

    //实现互联网访问接口
    @Override
    public void httpAccess(String url) 
        modem.httpAccess(url);
    

说明:

  1. 每个网络模块都变得更加简单了,我们只需要根据需求进行动态组装来实现不同代理。当用户要访问外网时,我们就用RouterProxy或者Modem生成基于互联网访问接口Internet的黑名单代理;当用户要访问内网时,我们就用交换机Switch生成基于局域网访问接口Intranet的黑名单代理。

5.客户端

public class Client 
    public static void main(String[] args) throws Exception 
        Internet internet = (Internet) Proxy.newProxyInstance(
                RouterProxy.class.getClassLoader(),
                RouterProxy.class.getInterfaces(),
                new BlackListFilter(new RouterProxy())
        );
        internet.httpAccess("http://www.电影.com");
        internet.httpAccess("http://www.游戏.com");
        internet.httpAccess("http://www.学习.com/java");
        internet.httpAccess("http://www.学习.com/java");

        Intranet intranet = (Intranet) Proxy.newProxyInstance(
                Switch.class.getClassLoader(),
                Switch.class.getInterfaces(),
                new BlackListFilter(new Switch())
        );
        intranet.fileAccess("\\\\\\\\192.67.1.2\\\\共享\\\\电影\\\\Human.mp4");
        intranet.fileAccess("\\\\\\\\192.67.1.2\\\\共享\\\\游戏\\\\Hero.exe");
        intranet.fileAccess("\\\\\\\\192.67.1.4\\\\shared\\\\Java学习资料.zip");
        intranet.fileAccess("\\\\\\\\192.67.1.6\\\\Java\\\\设计模式\\\\设计模式.doc");

    

输出结果:
拨号上网......连接成功
开启黑名单过滤功能......
禁止访问:http://www.电影.com
禁止访问:http://www.游戏.com
校验通过,转向实际业务
正在访问:http://www.学习.com/java
校验通过,转向实际业务
正在访问:http://www.学习.com/java
开启黑名单过滤功能......
禁止访问:\\\\192.67.1.2\\共享\\电影\\Human.mp4
禁止访问:\\\\192.67.1.2\\共享\\游戏\\Hero.exe
校验通过,转向实际业务
访问内网\\\\192.67.1.4\\shared\\Java学习资料.zip
校验通过,转向实际业务
访问内网\\\\192.67.1.6\\Java\\设计模式\\设计模式.doc

说明:

  1. 客户端调用了JDK提供的代理生成器Proxy的生产方法newProxyInstance(),并传入过滤器与路由器的相关参数,将过滤器功能与被代理对象组装在一起,动态生成代理对象,接着用它访问了若干互联网地址。
  2. 可以看到运行结果,路由器代理本身已经代理了“猫”的上网功能并加装了自动拨号功能,在此基础上外层的动态代理又加装了地址校验功能。
  3. 同理,代码为交换机加入过滤器功能并生成动态代理,接着用它访问了局域网中的文件,运行结果同样有效。无论用户访问互联网还是局域网,动态代理都充分完成了对网络地址访问的代理与管控工作。

6.扩展

其实在很多软件框架中都大量应用了动态代理的理念,如Spring的面向切面编程技术AOP,我们只需要定义好一个切面类@Aspect并声明其切入点@Pointcut(标记出被代理的哪些对象的哪些接口方法,类似于本章例程中的路由器与交换机的httpAccess以及fileAccess接口),以及被切入的代码块(要增加的逻辑,比如这里的过滤功能代码,可分为前置执行@Before、后置执行@After以及异常处理@AfterThrowing等),框架就会自动帮我们生成代理并切入目标。我们最常用到的就是给多个类方法前后动态加入写日志,此外还有为业务类加上数据库事务控制(业务代码开始前先切入“事务开始”,执行过后再切入“事务提交”,如果抛异常被捕获则执行“事务回滚”),如此就不必为每个业务类都写这些重复代码了,整个系统对数据库的访问都得到了事务管控,开发效率得到了提升。

总结

提示:这里对文章进行总结:

  1. 不管是在编程时预定义静态代理,还是在运行时即时生成代理,它们的基本理念都是通过拦截被代理对象的原始业务并在其之前或之后加入一些额外的业务或者控制逻辑,来最终实现在不改变原始类(被代理类)的情况下对其进行加工、管控。换句话说,虽然动态代理更加灵活,但它也是在静态代理的基础之上发展而来的。

  2. 我们来看代理模式的类结构

  • Subject(业务接口):对业务接口标准的定义与表示,对应本章例程中的互联网访问接口Internet。
  • RealSubject(被代理业务):需要被代理的实际业务类,实现了业务接口,对应本章例程中的调制解调器Modem。
  • Proxy(代理):同样实现了业务接口标准,包含被代理对象的实例并对其进行管控,对外提供代理后的业务方法,对应本章例程中的路由器RouterProxy。
  • Client(客户端):业务的使用者,直接使用代理业务,而非实际业务。
  1. 代理模式不仅能增强原业务功能,更重要的是还能对其进行业务管控。对用户来讲,隐藏于代理中的实际业务被透明化了,而暴露出来的是代理业务,以此避免客户端直接进行业务访问所带来的安全隐患,从而保证系统业务的可控性、安全性。。

以上是关于结构篇-代理模式的主要内容,如果未能解决你的问题,请参考以下文章

基础篇——代理模式之SpringAOP

设计模式 结构型模式 -- 代理模式(动态代理(JDK动态代理(JDK动态代理要求必须定义接口,对接口进行代理。)动态代理原理(使用arthas-boot.jar查看代理类的结构)动态代理的作用)(代

从零开始学习Java设计模式 | 结构型模式篇:代理模式

从零开始学习Java设计模式 | 结构型模式篇:代理模式

设计模式 结构型模式 -- 代理模式(代理模式概述结构静态代理动态代理)

Java进阶篇设计模式之七 ----- 享元模式和代理模式