前言

通常 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,

通常核心是不应该带有功能性的,而是一个生命周期和集成容器,

这样各功能可以通过相同的方式交互及扩展,并且任何功能都可以被替换,

如果做不到微核,至少要平等对待第三方,

即原作者能实现的功能,扩展者应该可以通过扩展的方式全部做到,

原作者要把自己也当作扩展者,这样才能保证框架的可持续性及由内向外的稳定性。

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

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

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

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  2. [转]Libev源码分析 -- 整体设计

    Libev源码分析 -- 整体设计 libev是Marc Lehmann用C写的高性能事件循环库.通过libev,可以灵活地把各种事件组织管理起来,如:时钟.io.信号等.libev在业界内也是广受好 ...

  3. Spring5源码分析(1)设计思想与结构

    1 源码地址(带有中文注解)git@github.com:yakax/spring-framework-5.0.2.RELEASE--.git Spring 的设计初衷其实就是为了简化我们的开发 基于 ...

  4. SOFA 源码分析 — 自动故障剔除

    前言 集群中通常一个服务有多个服务提供者.其中部分服务提供者可能由于网络,配置,长时间 fullgc ,线程池满,硬件故障等导致长连接还存活但是程序已经无法正常响应.单机故障剔除功能会将这部分异常的服 ...

  5. SOFA 源码分析 — 负载均衡和一致性 Hash

    前言 SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃). 一起看看他们的实现(重点还是一致性 hash) ...

  6. SOFA 源码分析— 事件总线

    前言 大部分框架都是事件订阅功能,即观察者模式,或者叫事件机制.通过订阅某个事件,当触发事件时,回调某个方法.该功能非常的好用,而 SOFA 内部也设计了这个功能,并且内部大量使用了该功能.来看看是如 ...

  7. SOFA 源码分析 — 链路数据透传

    前言 SOFA-RPC 支持数据链路透传功能,官方解释: 链路数据透传功能支持应用向调用上下文中存放数据,达到整个链路上的应用都可以操作该数据. 使用方式如下,可分别向链路的 request 和 re ...

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

    前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...

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

    章节简言 上一章笔者试着建一个Hello world的例子.是一个空白的struts2例子.明白了运行struts2至少需要用到哪一些Jar包.而这一章笔者将根据前面章节(Struts2 源码分析—— ...

随机推荐

  1. tar 压缩和解压缩使用笔记

    tar 压缩和解压缩使用笔记 1 文件 1.1 打包 1.1 压缩 $ tar czf myfile.txt.tar.gz ./myfile.txt 1.2 解压缩 解压缩到目录: $ mkdir o ...

  2. TCP的定时器系列 — SYNACK定时器

    主要内容:SYNACK定时器的实现,TCP_DEFER_ACCPET选项的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 在上一篇博客中,已经连带 ...

  3. 主线程中也不绝对安全的 UI 操作

    从最初开始学习 iOS 的时候,我们就被告知 UI 操作一定要放在主线程进行.这是因为 UIKit 的方法不是线程安全的,保证线程安全需要极大的开销.那么问题来了,在主线程中进行 UI 操作一定是安全 ...

  4. python import自己编写的模块:import json和simplejson

    python import的模块都是库里面的,而自己的模块也可以添加 比如python的json处理,库里没有json文件,import json不行. 网上大部分人告诉你处理json用 dumps和 ...

  5. javascript语法之Date对象与小案例

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. Unity热更新之C#反射动态获取类属性及方法

    如果我们要为发布出去的游戏更新一些功能,但又不想让用户重新下载整个游戏包,只让他下载我们更新的资源包,用assetBundle打包资源的方式是可以的,但其中有个最大的例外,那就是脚本. 虽然asset ...

  7. 2015年北京的第一场雪-关于android学习的思考(84)

    今天是2015年11月6日,今天北京下了大雪,我听着民谣,发现丢火车的摇滚也还不错,我身体的一部分毛发也发生了变异,由黑色变成红色,一切来的太突然了......不知不觉学习android开发2年多了, ...

  8. Cocos2D粒子发射器的纹理

    每个例子发射器只能使用单个纹理发射粒子. 如果你需要在相同粒子效果中组合多重纹理,你将不得不创建多重的发射器节点并且决定谁的粒子将在其它粒子之上或之下显示.

  9. 队列链式存储 - 设计与实现 - API函数

    队列相关基础内容参我的博文:队列顺序存储 - 设计与实现 - API函数 队列也是一种特殊的线性表:可以用线性表链式存储来模拟队列的链式存储. 主要代码: // linkqueue.h // 队列链式 ...

  10. ”()“和”[]“引发的血案——由此引出C++中关键词new

    先来看一个程序吧: #include <iostream> #include <cassert> using namespace std; int main() { ; int ...