前言
http://javatar.iteye.com/blog/1056664
通常 web 服务器在处理请求时, 都会使用过滤器模式, 无论是 Tomcat , 还是 Netty, 过滤器的好处是能够将处理的流程进行分离和解耦, 比如一个 Http 请求进入服务器, 可能需要解析 http 报头, 权限验证, 国际化处理等等, 过滤器可以很好的将这些过程隔离, 并且, 过滤器可以随时卸载, 安装.
每个 Web 服务器的过滤器思想都是类似的, 只是实现方式略有不同.
比如 Tomcat,Tomcat 使用了一个 FilterChain 对象保存了所有的 filter, 通过循环所有 filter 来完成过滤处理. 关于 Tomcat 的过滤器源码请看楼主之前的文章: 深入理解 Tomcat(九)源码剖析之请求过程 http://thinkinjava.cn/article/19
Netty 使用了 pipeline 作为过滤器管道, 管道中使用 handler 做拦截处理, 而 handler 使用一个 handlerInvoker(Context) 做隔离处理, 也就是将 handler 和 handler 隔离开来, 中间使用 这个 Context 上下文进行流转. 关于 Netty 的 pipeline 可以查看楼主之前的文章 : Netty 核心组件 Pipeline 源码分析 (一) 之剖析 pipeline 三巨头 http://thinkinjava.cn/article/68 Netty 核心组件 Pipeline 源码分析 (二) 一个请求的 pipeline 之旅 http://thinkinjava.cn/article/69
而 SOFA 使用了和上面的两个略有不同, 我们今天通过源码分析一下.
设计
SOFA 的过滤器由 3 个主要的类组成:
FilterInvoker 过滤器包装的 Invoker 对象, 主要是隔离了 filter 和 service 的关系;
Filter 过滤器(可通过 SPI 扩展)
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://juejin.im/post/5ae73ae46fb9a07aab29a28b