再学责任链和代理模式

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


前言

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

一、场景是什么?

1. 场景问题

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

在这里插入图片描述

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

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

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

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

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

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

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才能发起请求!");
        }
    }
}

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

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

10: 演示结果如下:

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

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

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

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

继承(个人理解的封装)

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

多态

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

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

结合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中的应用案例
  • 其他
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值