Dubbo已经有很多的内置过滤器,并且大多是默认启用的。不管是已实现的过滤器,自定义扩展的过滤器,启用方式有两种。
@Activate注解在应用启动时会加载并包装到Invoker中,可以通过配置group属性控制过滤器对Provider或Consumer生效,还可以通过order参数设置过滤器执行顺序,order越小,越先执行。例如:
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY, order = -10000)
<dubbo:provider filter="aaa,bbb"/>
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" filter="aaa,bbb"/>
<dubbo:consumer filter="aaa,bbb"/>
<dubbo:reference interface="com.alibaba.dubbo.demo.DemoService" filter="aaa,bbb"/>
用户自定义过滤器默认在框架内置过滤器之后执行,我们可以使用 filter = "aaa,default"
方式调整过滤器顺序。
写在前面的过滤器比后面的先执行。
可以使用filter = "-xxxFilter"
使xxxFilter不生效。filter = "-default"
可剔除所有默认过滤器。
如果服务端和消费端都配置了同一个过滤器,两边都会执行该过滤器。不会覆盖。
Dubbo实现的内置过滤器有十几种,包括服务端过滤器和消费端过滤器,以及特殊的MonitorFilter,它会在服务暴露和服务引用是同时包装到Invoker。
下面是部分过滤器功能列表:
过滤器名 | 使用方 | 作用 |
---|---|---|
AccessLogFilter | P | 打印每一次请求的日志 |
ActiveLimitFilter | C | 用于限制消费端对服务端的最大并行数 |
ExecuteLimitFilter | P | 用于限制服务端的最大并行数 |
ClassLoaderFilter | P | 用于切换不同线程的类加载器,服务调用完后会还原回去 |
ConsumerContextFilter | C | 为消费端把一些上下文信息设置到当前线程的RpcContext对象中,主要是invocation,local host,remote host |
ContextFilter | P | 为服务端把一些上下文信息设置到当前线程的RpcContext对象中 |
DeprecatedFilter | C | 如果被调用的方法标记为被弃用,DeprecatedFilter 会记录一条错误信息 |
EchoFilter | P | 用于回声测试 |
ExceptionFilter | P | 统一异常处理,防止序列化失败 |
GenericFilter | P | 服务端实现泛化调用,实现序列化检查和处理 |
GenericImplFilter | C | 消费端实现泛化调用,实现序列化检查和处理 |
TimeoutFilter | P | 如果服务调用超时,记录告警日志 |
TokenFilter | P | 服务端下发令牌给消费端,用于防止消费端绕过注册中心,直连服务端 |
TpsLimitFilter | P | 用于服务端限流 |
FutureFilter | C | 在发起invoke调用,或得到返回值,或出现异常时触发回调事件 |
MonitorFilter | P & C | 监控并统计所有接口的调用情况,如成功、失败、耗时。后续DubboMonitor会把该过滤器收集到的数据定时发送到Dubbo-Monitor服务上 |
Dubbo过滤器是在服务暴露(见https://blog.csdn.net/weixin_41172473/article/details/103301463)或服务引用(见https://blog.csdn.net/weixin_41172473/article/details/103524664)的时候加载并通过装饰器模式层层包装到Invoker上。
下面以服务端为例,探究服务端过滤器链的组装过程,消费端同理:
服务暴露有一个重要过程就是调用Protocol接口的export方法生成Exporter,将服务暴露出去,而这个protocol实例像Dubbo的许多组件一样,是通过ExtensionLoader加载的(关于Dubbo扩展加载机制见https://blog.csdn.net/weixin_41172473/article/details/102905655),
debug一下ExtensionLoader加载Protocol的过程:
可以看到,最后返回出去的不是DubboProtocol类型的实例,而是将DubboProtocol类型的实例包装成ProtocolFilterWrapper类型,当然还可能会有其他包装类型,比如ProtocolListenerWapper。
加载完成后,服务暴露过程调用protocol.export()
方法,就会走ProtocolFilterWrapper#export()
方法,这里面就封装了过滤器组装的逻辑。
在看组装过滤器代码之前,先看看Filter接口:
@SPI
public interface Filter {
Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}
这里的invoke方法的实现,其实就是织入自己的过滤器逻辑,然后调用Invoker的invoke方法,返回结果也是invoke方法的结果。
回到组装过滤器逻辑:
ProtocolFilterWrapper#export()
方法调用buildInvokerChain()
方法将Filter做层层包装,导出带有过滤器逻辑的Invoker。
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
// 暴露带有过滤器逻辑的Invoker
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
// 倒序遍历
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
//这里省略其他Override方法
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 调用里层filter的invoke方法
return filter.invoke(next, invocation);
}
};
}
}
return last;
}
代码比较简单,就是循环包装过滤器,Filter接口的invoke方法只是在调用真实Invoker.invoke()方法前后加入自己的逻辑,然后返回真实invoke调用的结果。这里过滤器链的顺序比较有意思,通过反向遍历filterList,组装到Invoker上,保证最后调用过滤器的顺序是正的,类似入栈和出栈。
Dubbo通过装饰器模式实现过滤器链,原理重点是一下三个过程:
protocol.export()
或者服务引用调用protocol.refer()
时,执行组装过滤器的逻辑。Invoker.invoke()
,在其调用前后加入各个过滤器自己的逻辑,实现过滤器链的织入。Dubbo内置的过滤器很多,有些默认启用,有些需要我们配置了才能生效,知道了过滤器的配置方式和实现原理,再去看具体每个过滤器的源码就很容易理解了,自己扩展一些过滤器也是可以的。