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,
通常核心是不应该带有功能性的,而是一个生命周期和集成容器,
这样各功能可以通过相同的方式交互及扩展,并且任何功能都可以被替换,
如果做不到微核,至少要平等对待第三方,
即原作者能实现的功能,扩展者应该可以通过扩展的方式全部做到,
原作者要把自己也当作扩展者,这样才能保证框架的可持续性及由内向外的稳定性。

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

原文地址:https://www.cnblogs.com/stateis0/p/8975289.html

时间: 2024-10-04 23:17:20

SOFA 源码分析 —— 过滤器设计的相关文章

SOFA 源码分析 —— 服务引用过程

前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引用服务的.实际上,基础逻辑和我们之前用 Netty 写的 RPC 小 demo 类似.有兴趣可以看看这个 demo-- 自己用 Netty 实现一个简单的 RPC. 示例代码 ConsumerConfig<HelloService> consumerConfig = new ConsumerCon

Nginx源码分析—架构设计思想

Nginx源码分析-架构设计思想 我任务nginx的源码可以分为三个部分,一个是在ngx_init_cycle之前,这个也算是为了重新启动nginx而准备的代码,比如说在这个时候可以接受外部的信号,也可以保存传递的参数,等等,当然在以后的函数中也考虑了是否正在重启nginx. 至于ngx_init_cycle这个函数,是一个很庞大的函数,在这个函数中可以看到调用了各个模块的钩子函数,这里又设计到了nginx结构体的使用,比如所有的模块都是ngx_module_t这个结构体,但是这个结构体中的ct

SOFA 源码分析 —— 服务发布过程

前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.alipay.sofa.rpc.quickstart.QuickStartServer 类下. ServerConfig serverConfig = new ServerConfig() .setProtocol("bolt") // 设置一个协议,默认bolt .setPort(9696)

Struts2 源码分析——过滤器(Filter)

章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析——核心机制)里的机制图片来分析源码.如果还不明白核心机制的朋友,请转到对应的章节进行阅读.笔者为了方便读者阅读,也把图片在次贴到了本章中.如下 根据图片笔者就明白我们首要分析便是橙黄色(Servlet Filters).也就是传说的过滤器(Filter).相信看过笔者前面几个章节的读者都明白strut

nginx源码分析--框架设计 &amp; master-worker进程模型

Nginx的框架设计-进程模型 在这之前,我们首先澄清几点事实: nginx作为一个高性能服务器的特点,其实这也是所有的高性能服务器的特点,依赖epoll系统调用的高效(高效是相对select/poll这些系统调用的,底层有一个链表和红黑树,避免了轮询,减少了用户空间和系统空间之间的数据传递等),非阻塞(所有的操作都是非阻塞,这样),多进程(master-slave进程模型),这些事实使得nginx成为一个高性能服务器的前提条件. 既然作为一个软件(http服务器),相对于一个网络库而言肯定有更

Struts2 源码分析——调结者(Dispatcher)之执行action

章节简言 上一章笔者写关于Dispatcher类如何处理接受来的request请求.当然读者们也知道他并非正真的执行action操作.他只是在执行action操作之前的准备工作.那么谁才是正真的执行action呢?本章笔者就带大家来看看StrutsExecuteFilter类的工作.在理解StrutsExecuteFilter类的工作之前,笔者还是希望大家回顾一下前一章讲到的request请求工作.为什么这样子讲呢?可以说StrutsExecuteFilter类的工作是建立在StrutsPrep

jQuery源码分析系列(33) : AJAX中的前置过滤器和请求分发器

jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求,分别是: 1.前置过滤器 jQuery. ajaxPrefilter 2.请求分发器 jQuery. ajaxTransport, 3.类型转换器 ajaxConvert 源码结构: jQuery.extend({ /** * 前置过滤器 * @type {[type]} */ ajaxPrefilter: addToPrefiltersOrTransports(prefilters), /** * 请求分发器 *

Java源码分析——String的设计

Tip:笔者马上毕业了,准备开始Java的进阶学习计划.于是打算先从String类的源码分析入手,作为后面学习的案例.这篇文章寄托着今后进阶系列产出的愿望,希望能坚持下去,不忘初心,让自己保持那份对技术的热爱. 因为学习分析源码,所以借鉴了HollisChuang成神之路的大部分内容,并在此基础上对源码进行了学习,在此感谢. 问题的引入 关于String字符串,对于Java开发者而言,这无疑是一个非常熟悉的类.也正是因为经常使用,其内部代码的设计才值得被深究.所谓知其然,更得知其所以然. 举个例

C++STL内存配置的设计思想与关键源码分析

说明:我认为要读懂STL中allocator部分的源码,并汲取它的思想,至少以下几点知识你要了解:operator new和operator delete.handler函数以及一点模板知识.否则,下面你很可能看不大明白,补充点知识再学习STL源码比较好. 下面会结合关键源码分析C++STL(SGI版本)的内存配置器设计思想.关键词既然是“思想”,所以重点也就呼之欲出了. 1.allocator的简短介绍 我阅读的源码是SGI公司的版本,也是看起来最清楚的版本,各种命名最容易让人看懂.alloc