zoukankan      html  css  js  c++  java
  • SOFA 源码分析 —— 过滤器设计

    前言

    通常 Web 服务器在处理请求时,都会使用过滤器模式,无论是 Tomcat ,还是 Netty,过滤器的好处是能够将处理的流程进行分离和解耦,比如一个 Http 请求进入服务器,可能需要解析 http 报头,权限验证,国际化处理等等,过滤器可以很好的将这些过程隔离,并且,过滤器可以随时卸载,安装。

    每个 Web 服务器的过滤器思想都是类似的,只是实现方式略有不同。

    比如 Tomcat,Tomcat 使用了一个 FilterChain 对象保存了所有的 filter,通过循环所有 filter 来完成过滤处理。关于 Tomcat 的过滤器源码请看楼主之前的文章:
    深入理解 Tomcat(九)源码剖析之请求过程

    Netty 使用了 pipeline 作为过滤器管道,管道中使用 handler 做拦截处理,而 handler 使用一个 handlerInvoker(Context) 做隔离处理,也就是将 handler 和 handler 隔离开来,中间使用 这个 Context 上下文进行流转。关于 Netty 的 pipeline 可以查看楼主之前的文章 :
    Netty 核心组件 Pipeline 源码分析(一)之剖析 pipeline 三巨头
    Netty 核心组件 Pipeline 源码分析(二)一个请求的 pipeline 之旅

    而 SOFA 使用了和上面的两个略有不同,我们今天通过源码分析一下。

    设计

    SOFA 的过滤器由 3 个主要的类组成:

    1. FilterInvoker 过滤器包装的Invoker对象,主要是隔离了filter和service的关系;
    2. Filter 过滤器(可通过 SPI 扩展)
    3. FilterChain 过滤器链起始接口,其实就是一个 Invoker。

    我们看看这 3 个类的主要方法,就知道如何设计的了。

    Filter 主要方法:

    public abstract SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException;
    

    invoke 方法,是一个抽象方法,用户可以自己实现,而方法体就是用户的处理逻辑。通常这个方法的结尾是:

    return invoker.invoke(request);
    

    调用了参数 invoker 对象的 invoke 方法。我们看看这个 FilterInvoker 。

    FilterInvoker 主要方法

    构造方法:

    public FilterInvoker(Filter nextFilter, FilterInvoker invoker, AbstractInterfaceConfig config) {
        this.nextFilter = nextFilter;
        this.invoker = invoker;
        this.config = config;
        if (config != null) {
            this.configContext = config.getConfigValueCache(false);
        }
    }
    

    楼主这里介绍一下他的主要构造方法。传入一个 filter,一个 invoker。

    这个 filter 就是当前 invoker 包装的过滤器,而参数 invoker 就是他的下一个 invoker 节点。当执行 FilterInvoker 的 invoke 方法的时候,通常会调用 filter 的 invoke 方法,并传入 invoker 参数。

    这就回到我们上面分析的 filter 的 invoke 方法,该方法内部会调用 invoker 的 invoke 方法,完成一次轮回。

    再看看 FilterChain 。

    FilterChain 主要方法

    FilterChain 是框架直接操作的实例,每个调用者都间接持有一个 FilterChain 实例,而这个实例相当于过滤器链表的头节点。

    构造方法:

    protected FilterChain(List<Filter> filters, FilterInvoker lastInvoker, AbstractInterfaceConfig config) {
        // 调用过程外面包装多层自定义filter
        // 前面的过滤器在最外层
        invokerChain = lastInvoker;
        if (CommonUtils.isNotEmpty(filters)) {
            loadedFilters = new ArrayList<Filter>();
            for (int i = filters.size() - 1; i >= 0; i--) {// 从最大的开始,从小到大开始执行
                Filter filter = filters.get(i);
                if (filter.needToLoad(invokerChain)) {
                    invokerChain = new FilterInvoker(filter, invokerChain, config);
                    // cache this for filter when async respond
                    loadedFilters.add(filter);
                }
            }
        }
    }
    

    在构造过滤器链的时候,会传入一个过滤器数组,并传入一个 FilterInvoker,这个 Invoker 是真正的业务方法,框架会在该 invoke 方法中反射调用接口的实现类,也就是业务代码。

    上面的构造方法主要逻辑是:

    倒序循环 List 中的 Filter 实例,将 Filter 用 FilterInvoker 封装,并传入上一个 FilterInvoker 到 FilterInvoker 的构造方法中,形成链表。而单独传入的 FilterInvoker 则会放到最后一个节点。`

    所以,最终,当 FilterChain 调用过滤器链的时候,会从 order 最小的过滤器开始,最后执行业务方法。

    注意:SOFA 过滤器中,真正执行业务方法的不是 Filter,而是 FilterInvoker 的具体实现类,在 invoke 方法中,会反射调用接口实现类的方法。原因是过滤器最后调用的 invoker.invoke。就不用再构造一个 filter 了。

    以上就是 SOFA 的过滤器设计。从总体上来讲,和 Tomcat 的 过滤器类似,只是 Tomcat 使用的数组,并且将 Service 区分看待,即执行完所有的过滤器后,执行 Service。而 SOFA 使用的是一个链表,并没有区分对待 Service。

    One more thing

    Filter 是个接口,并且标注了 @Extensible(singleton = false) 注解,表示这是一个扩展点,这个是 SOFA 微内核的一个设计。所有的中间件都可以通过扩展点加入到框架中。

    而扩展点其实有点类似 Spring 的 Bean,Spring Bean 和核心数据结构是 BeanDefine,SOFA 的 扩展点核心数据结构则是 ExtensionClass,该类定义了扩展点的所有相关信息。

    SOFA 会将所有的扩展点放在一个 ExtensionLoader 的 ConcurrentHashMap<String, ExtensionClass> 中。

    ExtensionLoader 可以称之为扩展类加载器,一个 ExtensionLoader 对应一个可扩展的接口。

    总结

    从设计上来说,SOFA 的过滤器更类似 Tomcat 的过滤器,相对于 Netty 的过滤器各有特色。Netty 的过滤器可以随时插拔,也许从业务上来说,SOFA 并不需要这样的功能吧。

    而同时,Filter 基于 SOFA 的扩展点来的。Dubbo 作者说过:

    大凡发展的比较好的框架,都遵守微核的理念,
    Eclipse的微核是OSGi, Spring的微核是BeanFactory,Maven的微核是Plexus,
    通常核心是不应该带有功能性的,而是一个生命周期和集成容器,
    这样各功能可以通过相同的方式交互及扩展,并且任何功能都可以被替换,
    如果做不到微核,至少要平等对待第三方,
    即原作者能实现的功能,扩展者应该可以通过扩展的方式全部做到,
    原作者要把自己也当作扩展者,这样才能保证框架的可持续性及由内向外的稳定性。

    微核插件式,平等对待第三方 对于框架来说,非常重要。

    作者:莫那鲁道

    出处: 博客园:http://www.cnblogs.com/stateis0/
                个人博客: thinkinjava.cn

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    b站评论爬取
    推算身份证的生日位
    mac安装mysql
    H3C V7版本的系统默认权限
    H3C IRF2的三种配置情况
    一张图看懂高通QC1.0-QC4.0快充进化之路!QC2.0跟QC3.0充电区别
    云服务器 ECS Linux 软件源自动更新工具
    透明代理、匿名代理、混淆代理、高匿代理有什么区别?
    ping正常但是ssh到linux服务器很卡的解决方法
    Python GUI编程(Tkinter) windows界面开发
  • 原文地址:https://www.cnblogs.com/stateis0/p/8975289.html
Copyright © 2011-2022 走看看