• 再学责任链和代理模式


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    现状是学过很多课程,很多框架,很多源码,但是总在用的时候就发现学过的东西都没办法得心应手的运用.写过很多设计模式的demo,实际项目中基本上没有用起来。想一想原因,还是学过的东西停留在最表层,没有深入到其本质.因此写这篇文章作为后面学习的一个标准实践,学的东西一定要实践起来,从0到1想一想.
    
    • 1

    一、场景是什么?

    1. 场景问题

    最近在做统一门户的集成,主要是完成A系统的前端嵌入到统一门户中去,A系统的用户默认是保持和统一门户的用户是同步的。嵌入后的结果是A系统的用户在统一门户完成登录后,通过跳转系统菜单到A系统。其中两方系统的架构方式如下: 
    
    • 1
    门户基本架构:

    在这里插入图片描述

    A系统架构:

    授权认证的技术栈主要是: Spring Security + oauth2 + redis
    在这里插入图片描述

    网关承担的职责如下:

    在这里插入图片描述
    这里再详细描述一下A系统接入统一门户的改造点及原因:
    首先A系统的登录功能被去掉了,只能使用统一门户的登录功能,意味着A系统的token也将无法获取,意味着A系统的交易经过网关的时候token验证无法通过;
    涉及系统集成时的改造时有规则如下:
    1: 尽量不改动已有代码(改动越小,影响越小);
    2: 尽量使用扩展机制进行集成;
    3: 尽量做新增不做代码的修改;

    通过分析,A系统集成到统一门户需要做的事情如下:
    1: 如果要保持A系统架构不变,那么必须要保留A系统的登录功能;
    2: 在保留A系统登录功能的情况下,统一门户的用户不能看到A系统的登录界面,所以只能做前端做一个loading,在loading里面做自动登录的逻辑;
    3: 于是在A系统集成统一门户之后,会存在2个token,一个是统一门户的,一个是A系统;
    4: 那么问题来了, 如何保证A系统的token和统一门户的同步呢?
    2个token的生成如下:

    在这里插入图片描述

    2. 解决方案一

    通过绑定门户token和A系统token关系,后台进行定时任务判断只要有其中一个token失效,就让2个token同时失效;
    弊端:
    1: 定时任务需要去不停的调用接口,浪费CPU等资源,做的都是无意义的循环逻辑;
    2: 定时任务会频繁地调用接口,浪费网络资源;
    3: 定时任务本身和token的失效时间存在误差,不能精确清除失效token
    4: 需要使用容器在后端内存存储双token的绑定关系并且需要维护;
    综上考虑,使用定时任务并不可取;

    3. 解决方案二

    首先定一个约定,在A集成到统一门户之后,按照道理说,A系统的token其实是无意义的存在,只不过为了维护原框架的不变性,才保留下来的,所以我们可以规定门户的token优先级高于A系统的token, 意思如下:

    在这里插入图片描述
    上图中隐藏了一个点是:
    当门户的token未过期的时候,而A系统的token却过期了,按照正常的情况,A系统应该继续能做交易,所以需要将A系统的token续期;

    还有一个问题是: A系统内部的交易并不会对门户的token进行续期,所以时间已久,门户的token会过期,所以网关需要对门户的token做一个续期的操作,图示:

    在这里插入图片描述
    这个插件主要是通过开发小jar包的方式实现的;

    下面这个插件机制开发方式可以作为基础平台中的一种方式,对外提供了高度的可定制接口(符合开闭原则);

    在这里插入图片描述

    二、抽象过程?

    1.抽象流程

    网关本身就是一个过滤器链(责任链设计模式);

    在这里插入图片描述
    先画一张图表明演示的责任链模式:

    在这里插入图片描述
    代码如下:
    1: Filter核心接口定义

    public interface Filter {
    
        /**
         *
         * @param contextExchange
         * @param chain
         */
        void filter(ServletContextExchange contextExchange, FilterChain chain);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2: 核心上下文对象定义

    public class ServletContextExchange {
    
        private Request request;
        private Response response;
    
        public ServletContextExchange() {
        }
    
        public ServletContextExchange(Request request, Response response) {
            this.request = request;
            this.response = response;
        }
    
        public Request getRequest() {
            return request;
        }
    
        public void setRequest(Request request) {
            this.request = request;
        }
    
        public Response getResponse() {
            return response;
        }
    
        public void setResponse(Response response) {
            this.response = response;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    3: 核心FilterChain定义

    
    /**
     * @author 
     * @version V1.0
     * @package com.hd.mybatis.simple.gateway
     * @date 2022/9/18 9:52
     * @desc 链式容器对象
     *  需要对Filter的存储容器进行维护和遍历访问
     *
     *  这里面有Filter对象 所以不能放着玩的吧, 所以理所当然地存在方法:
     *  filter.filter(context) -> 推出有一个 Context 上下文对象;
     *  并且 Context是必须存在的; todo 就算 Context是必须存在的, 如果这样用还是耦合太高了
     *  所以使用 参数传参的方式进行 doFilter
     *  后面可以优化成一个接口 提供默认实现玩玩;
     *
     * @copyright @ 2022-2030 hduong
     */
    public class FilterChain {
    
        /**
         * 请求响应上下文对象;
         *
         */
    //    private ServletContextExchange contextExchange;
        private List<Filter> filterList = new ArrayList<>();
        private int index = 0;
    //
    //    public FilterChain(ServletContextExchange contextExchange) {
    //        this.contextExchange = contextExchange;
    //    }
    
    //    public FilterChain(ServletContextExchange contextExchange, List filterList) {
    //        this.contextExchange = contextExchange;
    //        this.filterList = filterList;
    //    }
    
        /**
         * 新增过滤器
         *  小技巧 -.> 链式调用
         * @param filter
         */
        public void addFilter(Filter filter) {
            this.filterList.add(filter);
        }
    
        /**
         * 支持链式调用
         * @param filter
         * @return
         */
        public FilterChain filter(Filter filter) {
            this.filterList.add(filter);
            return this;
        }
    
        /**
         *
         * @param servletContextExchange
         */
        public void doFilter(ServletContextExchange servletContextExchange) {
            //1: 遍历过滤器链
            //2: 走过滤器的 filter方法逻辑
            //3: 需要记录走到哪个位置了
            //相当于记录开始和结束点
            if (index == filterList.size()) {
                System.out.println("链路执行完毕.");
                return;
            }
            Filter filter = filterList.get(index);
            //继续下一个过滤器
            index++;
            filter.filter(servletContextExchange, this);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    4: Request对象定义

    
    public class Header {
    
        private String authorization;
        private String authValue;
    
        public Header(String authorization) {
            this.authorization = authorization;
        }
    
        public Header() {
        }
    
        public String getAuthorization() {
            return authorization;
        }
    
        public void setAuthorization(String authorization) {
            this.authorization = authorization;
        }
    
        public String getAuthValue() {
            return authValue;
        }
    
        public void setAuthValue(String authValue) {
            this.authValue = authValue;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    5: Response对象定义

    
    public class Response {
    
        private String status;
        private String message;
    
        public Response() {
        }
    
        public Response(String status, String message) {
            this.status = status;
            this.message = message;
        }
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    6: Header对象定义

    
    public class Header {
    
        private String authorization;
        private String authValue;
    
        public Header(String authorization) {
            this.authorization = authorization;
        }
    
        public Header() {
        }
    
        public String getAuthorization() {
            return authorization;
        }
    
        public void setAuthorization(String authorization) {
            this.authorization = authorization;
        }
    
        public String getAuthValue() {
            return authValue;
        }
    
        public void setAuthValue(String authValue) {
            this.authValue = authValue;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    7: SecurityFilter过滤器实现

    
    public class SecurityFilter implements Filter {
    
        /**
         *
         * @param contextExchange
         * @param chain
         */
        @Override
        public void filter(ServletContextExchange contextExchange, FilterChain chain) {
            Request request = contextExchange.getRequest();
            System.out.println("1- 开始安全校验");
            checkRequest(request);
            System.out.println("1- 安全验证通过, 继续下一个过滤器处理!");
            chain.doFilter(contextExchange);
        }
    
        /**
         *
         * @param request
         */
        private void checkRequest(Request request) {
            if (request == null) {
                throw new RuntimeException("请求对象为空!");
            }
            String name = request.getName();
            if (!"admin".equals(name)) {
                throw new RuntimeException("假设admin才能发起请求!");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    8: TokenFilter过滤器实现

    
    public class TokenFilter implements Filter {
    
        /**
         * 模拟 token缓存
         */
        private static Map<String, String> tokenCache = new HashMap<>();
        static {
            tokenCache.put("authorization", "1111");
            tokenCache.put("authorization2", "2222");
            tokenCache.put("authorization3", "3333");
        }
    
        /**
         * 完成token校验
         * @param contextExchange
         * @param chain
         */
        @Override
        public void filter(ServletContextExchange contextExchange, FilterChain chain) {
            System.out.println("2- 开始token校验");
            Request request = contextExchange.getRequest();
            checkRequest(request);
            System.out.println("2- token校验通过,继续下一个过滤器处理");
            chain.doFilter(contextExchange);
        }
    
        /**
         *
         * @param request
         */
        private void checkRequest(Request request) {
            Header header = request.getHeader();
            if (header == null) {
                throw new RuntimeException("授权未通过!Header为null!");
            }
            String authorization = header.getAuthorization();
            if (authorization == null || "".equals(authorization)) {
                throw new RuntimeException("authorization为null!");
            }
            String token = tokenCache.get(authorization);
            if (token == null || "".equals(token.trim())) {
                throw new RuntimeException("授权未通过, 不是登录的token!");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    9: 最后一个演示DemoController实现

    
    /**
     * @author 
     * @version V1.0
     * @package com.hd.mybatis.simple.gateway.controller
     * @date 2022/9/18 10:40
     * @desc
     *  演示 责任链的 请求使用
     *  这里将 controller放到最后的一个责任链对象中;
     *  在 SpringMVC源码中是会存在映射关系的:
     *  url -> controller 的 method方法;
     *
     * @copyright @ 2022-2030 hduong
     */
    public class DemoController implements Filter {
    
        /**
         * 执行业务逻辑:
         * 暂时放到过滤器链 中 ;
         *
         * @param contextExchange
         * @param chain
         */
        @Override
        public void filter(ServletContextExchange contextExchange, FilterChain chain) {
            //打印请求参数即可
            Request request = contextExchange.getRequest();
            String params = request.getParams();
            System.out.println("结果- 执行业务逻辑: " + params);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    10: 演示结果如下:

    1- 开始安全校验
    1- 安全验证通过, 继续下一个过滤器处理!
    2- 开始token校验
    2- token校验通过,继续下一个过滤器处理
    结果- 执行业务逻辑: 请求参数待处理.
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注: 这里仅仅是演示了一下责任链模式.

    设计模式的运用需要一种更强大的抽象能力,追其本质还是面向对象的三大特征:
    封装 (个人理解的封装)

    如果我们不局限于定义, 一切皆可封装. 假如我们首先认为一切皆可封装,那么就先去封装;
    比如做一个东西时,我们不管三七二十一,先封装起来。后面发现封装的太大了,大乱了,于是就要进行分离,那么如何进行分离呢? 主要分为两类:
    第一类就是逻辑上的共性分离 -> 引申出继承;
    第二类就是抽象上的共性分离 -> 引出多态;

    继承(个人理解的封装)

    继承就是封装的一种体现, 将公共的东西都写到一起,后面的人用的时候只需要继承一下即可.

    多态

    多态就是多种形态,一个具体的事物会有多种形态吗? 正常思考下是不会这么认为的。
    那么就是说抽象的事物才会有多种形态,抽象的事物会有多种具体表现的形态。

    综上所述: 个人还是认为面向对象编程用到极致,本质就是封装!
    注: (个人的谬论,不作他人参考!)

    结合spring可以通过定义一个只有接口的工程,然后被其他工程引用,就可以开发相关的插件机制;

    这块在spring相关的文章会详述。

    mybatis的插件机制用到的动态代理模式, 注解, 责任链模式等等

    参考: 非常不错的文章

    https://www.cnblogs.com/qdhxhz/p/11390778.html
    https://www.jianshu.com/p/b82d0a95b2f3

    2.模拟方案一实现

    方案一被pass,未做,仅仅作为思考过程记录.

    3.模拟方案二实现

    方案二模拟实现待后续…


    引入责任链

    搜索个多个责任链的源码,关于这块还有许多东西值得实践.

    1. 审批流责任链(职责集中在链路上的一个对象)

    经典案例: 审批流程
    贴图如下:

    在这里插入图片描述

    2. 请求处理型责任链(职责分散在链路上的每一个对象)

    经典案例: 过滤器等

    参考上面的网关责任链,以下待办:

    • 责任链综合其他模式及场景实践
    • 责任链扩展的Pipeline案例
    • 责任链结合策略工厂模式案例
    • 责任链在Zookeeper中的应用案例
    • 责任链在Security中结合代理的应用案例
    • 责任链在Netty中的应用案例
    • 责任链在Dubbo中的应用案例
    • 其他
  • 相关阅读:
    [网络工程师]-防火墙-防火墙体系
    信息系统项目管理师第四版学习笔记——项目管理科学基础
    FPGA-VGA成像原理与时序
    Liunx环境安装字体(simsun为例)
    安装lrzsz
    UltraEdit文本编辑器下载适用于Win/Mac和Linux
    基于HFSS的T型功分波导设计
    当当网API 获取商品详情 电商API数据
    微信小程序开启横屏调试
    面试知识储备--打包工具篇(webpack和vite)
  • 原文地址:https://blog.csdn.net/qq_34629467/article/details/126911871