Dubbo的Filter在使用的过程中是我们扩展最频繁的内容,而且Dubbo的很多特性实现也都离不开Filter的工作,今天一起来看一下Filter的具体实现。

Filter(过滤器)在很多框架中都有使用过这个概念,基本上的作用都是类似的,在请求处理前或者处理后做一些通用的逻辑,而且Filter可以有多个,支持层层嵌套。
Dubbo的Filter概念基本上符合我们正常的预期理解,而且Dubbo官方针对Filter做了很多的原生支持,目前大致有20来个吧,包括我们熟知的RpcContext,accesslog功能都是通过filter来实现了,下面一起详细看一下Filter的实现。
Dubbo的Filter实现入口是在ProtocolFilterWrapper,因为ProtocolFilterWrapper是Protocol的包装类,所以会在加载的Extension的时候被自动包装进来(理解这里的前提是理解Dubbo的SPI机制),然后我们看一下这个Filter链是如何构造的。

  public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//向注册中心引用服务的时候并不会进行filter调用链
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
} private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
//获得所有激活的Filter(已经排好序的)
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (filters.size() > 0) {
for (int i = filters.size() - 1; i >= 0; i --) {
final Filter filter = filters.get(i);
//复制引用,构建filter调用链
final Invoker<T> next = last;
//这里只是构造一个最简化的Invoker作为调用链的载体Invoker
last = new Invoker<T>() { public Class<T> getInterface() {
return invoker.getInterface();
} public URL getUrl() {
return invoker.getUrl();
} public boolean isAvailable() {
return invoker.isAvailable();
} public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
} public void destroy() {
invoker.destroy();
} @Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}

看到上面的内容,我们大致能明白实现是这样子的,通过获取所有可以被激活的Filter链,然后根据一定顺序构造出一个Filter的调用链,最后的调用链大致是这样子:Filter1->Filter2->Filter3->......->Invoker,这个构造Filter链的逻辑非常简单,重点就在于如何获取被激活的Filter链。

//将key在url中对应的配置值切换成字符串信息数组
public List<T> getActivateExtension(URL url, String key, String group) {
String value = url.getParameter(key);
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
} public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
//所有用户自己配置的filter信息(有些Filter是默认激活的,有些是配置激活的,这里这里的names就指的配置激活的filter信息)
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); //如果这些名称里不包括去除default的标志(-default),换言之就是加载Dubbo提供的默认Filter
if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
//加载extension信息
getExtensionClasses();
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
//name指的是SPI读取的配置文件的key
String name = entry.getKey();
Activate activate = entry.getValue();
//group主要是区分实在provider端生效还是consumer端生效
if (isMatchGroup(group, activate.group())) {
T ext = getExtension(name);
//这里以Filter为例:三个判断条件的含义依次是:
//1.用户配置的filter列表中不包含当前ext
//2.用户配置的filter列表中不包含当前ext的加-的key
//3.如果用户的配置信息(url中体现)中有可以激活的配置key并且数据不为0,false,null,N/A,也就是说有正常的使用
if (! names.contains(name)
&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) {
exts.add(ext);
}
}
}
//根据Activate注解上的order排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
//进行到此步骤的时候Dubbo提供的原生的Filter已经被添加完毕了,下面处理用户自己扩展的Filter
List<T> usrs = new ArrayList<T>();
for (int i = 0; i < names.size(); i ++) {
String name = names.get(i);
//如果单个name不是以-开头并且所有的key里面并不包含-'name'(也就是说如果配置成了"dubbo,-dubbo"这种的可以,这个if是进不去的)
if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
//可以通过default关键字替换Dubbo原生的Filter链,主要用来控制调用链顺序
if (Constants.DEFAULT_KEY.equals(name)) {
if (usrs.size() > 0) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
//加入用户自己定义的扩展Filter
T ext = getExtension(name);
usrs.add(ext);
}
}
}
if (usrs.size() > 0) {
exts.addAll(usrs);
}
return exts;
}

Cunsumer

ConsumerContextFilter (默认触发)

 public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
//在当前的RpcContext中记录本地调用的一次状态信息
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {
((RpcInvocation)invocation).setInvoker(invoker);
}
try {
return invoker.invoke(invocation);
} finally {
RpcContext.getContext().clearAttachments();
}
}

其实简单来看这个Filter的话是十分简单,它又是怎么将客户端设置的隐式参数传递给服务端呢?载体就是Invocation对象,在客户端调用Invoker.invoke方法时候,会去取当前状态记录器RpcContext中的attachments属性,然后设置到RpcInvocation对象中,在RpcInvocation传递到provider的时候会通过另外一个过滤器ContextFilter将RpcInvocation对象重新设置回RpcContext中供服务端逻辑重新获取隐式参数。这就是为什么RpcContext只能记录一次请求的状态信息,因为在第二次调用的时候参数已经被新的RpcInvocation覆盖掉,第一次的请求信息对于第二次执行是不可见的。

ActiveLimitFilter (当配置了actives并且值不为0的时候触发)

ActiveLimitFilte主要用于限制同一个客户端对于一个服务端方法的并发调用量。(客户端限流)

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
URL url = invoker.getUrl();
String methodName = invocation.getMethodName();
int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
//主要记录每台机器针对某个方法的并发数量
RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
if (max > 0) {
long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
long start = System.currentTimeMillis();
long remain = timeout;
int active = count.getActive();
if (active >= max) {
synchronized (count) {
//这个while循环是必要的,因为在一次wait结束后,可能线程调用已经结束了,腾出来consumer的空间
while ((active = count.getActive()) >= max) {
try {
count.wait(remain);
} catch (InterruptedException e) {
}
//如果wait方法被中断的话,remain这时候有可能大于0
//如果其中一个线程运行结束自后调用notify方法的话,也有可能remain大于0
long elapsed = System.currentTimeMillis() - start;
remain = timeout - elapsed;
if (remain <= 0) {
throw new RpcException("...");
}
}
}
}
}
try {
//调用开始和结束后增减并发数量
long begin = System.currentTimeMillis();
RpcStatus.beginCount(url, methodName);
try {
Result result = invoker.invoke(invocation);
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;
} catch (RuntimeException t) {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
throw t;
}
} finally {
//这里很关键,因为一个调用完成后要通知正在等待执行的队列
if(max>0){
synchronized (count) {
count.notify();
}
}
}
}

FutureFilter

Future主要是处理事件信息,主要有以下几个事件:

  • oninvoke 在方法调用前触发(如果调用出现异常则会直接触发onthrow方法)
  • onreturn 在方法返回会触发(如果调用出现异常则会直接触发onthrow方法)
  • onthrow 调用出现异常时候触发
public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
// 这里主要处理回调逻辑,主要区分三个时间:oninvoke:调用前触发,onreturn:调用后触发 onthrow:出现异常情况时候触发
fireInvokeCallback(invoker, invocation);
//需要在调用前配置好是否有返回值,已供invoker判断是否需要返回future.
Result result = invoker.invoke(invocation);
if (isAsync) {
asyncCallback(invoker, invocation);
} else {
syncCallback(invoker, invocation, result);
}
return result;
} private void fireInvokeCallback(final Invoker<?> invoker, final Invocation invocation) {
final Method onInvokeMethod = (Method)StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_METHOD_KEY));
final Object onInvokeInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_INVOKE_INSTANCE_KEY)); if (onInvokeMethod == null && onInvokeInst == null ){
return ;
}
if (onInvokeMethod == null || onInvokeInst == null ){
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() +" has a onreturn callback config , but no such "+(onInvokeMethod == null ? "method" : "instance")+" found. url:"+invoker.getUrl());
}
//由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
if (onInvokeMethod != null && ! onInvokeMethod.isAccessible()) {
onInvokeMethod.setAccessible(true);
}
//从之类可以看出oninvoke的方法参数要与调用的方法参数一致
Object[] params = invocation.getArguments();
try {
onInvokeMethod.invoke(onInvokeInst, params);
} catch (InvocationTargetException e) {
fireThrowCallback(invoker, invocation, e.getTargetException());
} catch (Throwable e) {
fireThrowCallback(invoker, invocation, e);
}
} //fireReturnCallback的逻辑与fireThrowCallback基本一样,所以不用看了
private void fireThrowCallback(final Invoker<?> invoker, final Invocation invocation, final Throwable exception) {
final Method onthrowMethod = (Method)StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_METHOD_KEY));
final Object onthrowInst = StaticContext.getSystemContext().get(StaticContext.getKey(invoker.getUrl(), invocation.getMethodName(), Constants.ON_THROW_INSTANCE_KEY)); if (onthrowMethod == null && onthrowInst == null ){
return ;
}
if (onthrowMethod == null || onthrowInst == null ){
throw new IllegalStateException("service:" + invoker.getUrl().getServiceKey() +" has a onthrow callback config , but no such "+(onthrowMethod == null ? "method" : "instance")+" found. url:"+invoker.getUrl());
}
if (onthrowMethod != null && ! onthrowMethod.isAccessible()) {
onthrowMethod.setAccessible(true);
}
Class<?>[] rParaTypes = onthrowMethod.getParameterTypes() ;
if (rParaTypes[0].isAssignableFrom(exception.getClass())){
try {
//因为onthrow方法的参数第一个值必须为异常信息,所以这里需要构造参数列表
Object[] args = invocation.getArguments();
Object[] params; if (rParaTypes.length >1 ) {
//原调用方法只有一个参数而且这个参数是数组(单独拎出来计算的好处是这样可以少复制一个数组)
if (rParaTypes.length == 2 && rParaTypes[1].isAssignableFrom(Object[].class)){
params = new Object[2];
params[0] = exception;
params[1] = args ;
}else {//原调用方法有多于一个参数
params = new Object[args.length + 1];
params[0] = exception;
System.arraycopy(args, 0, params, 1, args.length);
}
} else {//原调用方法没有参数
params = new Object[] { exception };
}
onthrowMethod.invoke(onthrowInst,params);
} catch (Throwable e) {
logger.error(invocation.getMethodName() +".call back method invoke error . callback method :" + onthrowMethod + ", url:"+ invoker.getUrl(), e);
}
} else {
logger.error(invocation.getMethodName() +".call back method invoke error . callback method :" + onthrowMethod + ", url:"+ invoker.getUrl(), exception);
}
}

等等

@Activate注解使用:

外部dubbo filter,作为jar包引入其他工程,可以直接拦截dubbo请求

@Activate(group = Constants.PROVIDER)
public class DubboProviderFilter implements Filter { private static Logger log = LoggerFactory.getLogger(DubboProviderFilter.class); @Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { Class<?> clazz = invoker.getInterface();
Object[] requestParameters = invocation.getArguments();
String version = invocation.getAttachments().get(Constants.VERSION_KEY);
String fullName = clazz.getName();
String methodName = invocation.getMethodName();
String simpleClassName = clazz.getSimpleName();
//过滤处理逻辑
if(TestProcesser.isDubboTarget(fullName, simpleClassName, methodName, version,requestParameters)) {
try {
Object result = TestProcesser.callMethodForDubbo(fullName, methodName, version, requestParameters);
return new RpcResult(result);
}catch (Exception e) {
log.error("defult error:",e);
}
}
//走原有逻辑
return invoker.invoke(invocation);
} }

dubbo,hessian过滤器filter使用的更多相关文章

  1. dubbo中的Filter链原理及应用

    转载:https://www.jianshu.com/p/f390bb88574d filter在dubbo中的应用非常广泛,它可以对服务端.消费端的调用过程进行拦截,从而对dubbo进行功能上的扩展 ...

  2. Angularjs在控制器(controller.js)的js代码中使用过滤器($filter)格式化日期/时间实例

    Angularjs内置的过滤器(filter)为我们的数据信息格式化提供了比较强大的功能,比如:格式化时间,日期.格式化数字精度.语言本地化.格式化货币等等.但这些过滤器一般都是在VIEW中使用的,比 ...

  3. 网站过滤器Filter

    实际上,Filter与Servlet及其相似,区别只是FIlter的doFilter()方法里多了一个FilterChain的参数,通过该参数可以控制是否放行用户的请求.网站有了过滤器十分的方便,可以 ...

  4. paip.输出内容替换在Apache 过滤器filter的设置

    paip.输出内容替换在Apache 过滤器filter的设置 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog ...

  5. java Servlet中的过滤器Filter

    web.xml中元素执行的顺序listener->filter->struts拦截器->servlet. 1.过滤器的概念 Java中的Filter 并不是一个标准的Servlet ...

  6. [ionic开源项目教程] - 第6讲 过滤器filter的使用

    过滤器filter的使用 1.回顾 再熟悉一下tab1.html的代码: <div class="list"> <a ng-repeat="item i ...

  7. selvert的过滤器filter处理中文乱码

    注意问题:在学习用selvert的过滤器filter处理中文乱码时,在filter配置初始化时用了utf-8处理中文乱码,而在提交的jsp页面中却用了gbk.虽然两种都可以出来中文乱码,但是却造成了处 ...

  8. MVC之 自定义过滤器(Filter)

    MVC之 自定义过滤器(Filter) 一.自定义Filter 自定义Filter需要继承ActionFilterAttribute抽象类,重写其中需要的方法,来看下ActionFilterAttri ...

  9. Servlet中的过滤器Filter用法

    1.过滤器的概念 Java中的Filter 并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应. 主要用于对HttpServletRequest 进行预处理,也可以对Http ...

随机推荐

  1. profile对比std::regex与boost::regex的性能

    c++11标准库的regex比boost库的regex之间的性能差距接近5倍,这是为什么?stackflow上也找到一篇post<c++11 regex slower than python&g ...

  2. 有关Kafka的那些事

    Kafka基本概念 Producer: 消息和数据的生产者,向kafka的一个topic发布消息的进程.代码.服务. Consumer:消息和数据的消费者,订阅数据并且处理器发布的消息的进程.代码.服 ...

  3. spring初始(介绍、核心架构)

    1.spring介绍 Spring是个java企业级应用的开源开发框架.主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring框架目标是简化Java企业级应用开发,并通 ...

  4. Dominate【操作系统的经典算法】

    此篇文章我们来谈一谈操作系统中都出现过哪些算法,请欣赏下图 ↓ 进程和线程管理中的算法 进程和线程在调度时候出现过很多算法,这些算法的设计背景是当一个计算机是多道程序设计系统时,会频繁的有很多进程或者 ...

  5. jquery,Datatables插件使用,做根据【日期段】筛选数据的功能 jsp

     时间格式为yyyymmdd,通过转换为int类型进行比较大小 画面: jsp代码: 1 //日期显示控件,使用h-ui框架 2 3 <div class="text-c"& ...

  6. C 面向对象编程 --- 一模块的串口协议解析

    // 任务目的// 解析串口收到的54个字节.这54个字节包含了8个车道的5大信息以及校验信息.// 实现了查询每条车道包含了哪些信息. #include <stdio.h>#includ ...

  7. 【数量技术宅|量化投资策略系列分享】股指期货IF分钟波动率统计策略

    更多精彩内容,欢迎关注公众号:数量技术宅.想要获取完整策略代码,请加技术宅微信:sljsz01 股指期货分钟级别波动率观察 在A股市场,股指期货是由一揽子股票组成的股票现货指数,所对应的期货.由于期货 ...

  8. vue : 无法加载文件 C:\Users\Lenovo\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。

    第一步:用管理员身份打开 第二步:执行:set-ExecutionPolicy RemoteSigned 选择Y或A,回车

  9. Fabric1.4.4 基础环境搭建

    简单记录一下fabric版本1.4.4的环境搭建部署,运行环境为CentOs7.8,如有错误欢迎批评指正. 1.Docker 和 Docker Compose 1. docker的安装部署 docke ...

  10. docker容器命令1

    docker容器命令 新建并启动容器命令 docker run INAME(镜像名字) 语法:docker run [OPTIONS] INAME [COMMAND] 例子:docker run -i ...