Dubbo引入过滤器链机制来实现功能的包装(或扩展)。Dubbo很多功能,例如泛化调用、并发控制等都是基于Filter机制实现的,系统默认的Filter在/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter文件中定义,内容如下:
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
以其中一个来说明一下Filter的定义要素:
/**
* EchoInvokerFilter
*/
@Activate(group = Constants.PROVIDER, order = -110000) // @2
public class EchoFilter implements Filter { // @1
@Override
public Result invoke(Invoker> invoker, Invocation inv) throws RpcException {
if (inv.getMethodName().equals(Constants.$ECHO) && inv.getArguments() != null && inv.getArguments().length == 1)
return new RpcResult(inv.getArguments()[0]);
return invoker.invoke(inv);
}
}
代码@1:实现com.alibaba.dubbo.rpc.Filter接口。
代码@2:添加Activate,其注解含义如下:
除了支持默认的过滤器外,Dubbo还支持自定义Filter,可以通过service.filter指定过滤器,多个用英文逗号隔开,其配置方法为:
当然,可以为所有服务提供者设置共用过滤器,其指定方法为:
消费端自定义过滤器的key为reference.filter,其使用方法在< dubbo:reference/>标签或< dubbo:consumer/>标签下定义属性。
调用链的构建是通过下面的方法来实现:
com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.buildInvokerChain(Invoker, String, String)
该方法源码如下:
private static Invoker buildInvokerChain(final Invoker invoker, String key, String group) {
Invoker last = invoker;
List 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);
final Invoker next = last;
last = new Invoker() {
public Class 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列表,然后创建与Fitler数量一样多Invoker结点,接着将这些结点串联在一起,构成一个链表,最后将这个链的首结点返回,随后的调用中,将从首结点开始,依次调用各个结点,完成调用后沿调用链返回。这里各个Invoker结点的串联是通过与其关联的invoke方法来完成的。接下来分析这个调用链的创建。
buildInvokerChain(final Invoker invoker, String key, String group)
调用buildInvokerChain时会传入invoker参数:
Invoker last = invoker;
通过创建last,来指向头结点。初始状态如下图所示:
接着通过循环遍历获取到的Filter,同时创建Invoker结点,每个结点对应一个Filter。此时循环内部定义了next指针。
final Invoker next = last;
该指针每次更新为指向原链表中的头结点,即last指针。 随后创建新结点,并更新last。此时新结点将作为链表中的头结点。
final Invoker next = last;
last = new Invoker(),
结果如下图所示。
接着通过invoke方法将各个结点串联。
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
在该方法内部,通过调用与该invoker关联的filter中的invoke方法来实现结点的连接。调用时将next传入invoke方法,在调用时首先会调用该结点对应的filter的invoke方法,接着调用传入参数next的invoke方法。Next的invoke方法同样会调用自己所关联的filter的invoke方法,这样就完成了结点的串联。
可以看到链表的最后一个结点就是buildInvokerChain 方法的入参invoker。最终buildInvokerChain方法通过链表头插法完成调用链的创建。因此在真正的调用请求处理前会经过若干filter进行预处理。
这里调用链的创建可以看作是职责链模式(Chain of Responsibility Pattern)的一个实现。这样系统中增加一个新的过滤器预处理请求时,无须修改原有系统的代码,只需重新建调用链即可。
以为dubbo接口增加IP白名单为例,在开发dubbo接口时,有时可能会限制接口的访问,ip白名单即是一种。在dubbo中,通过扩展Filter接口,可以实现IP白名单的功能。
先定义一个配置IP白名单的bean:
/**
* Created by j.tommy on 2017/11/4.
*/
public class IPWhiteList {
private boolean isEnabled; // 是否启用白名单
private List allowIps; // 允许的白名单列表
public boolean isEnabled() {
return isEnabled;
}
public void setEnabled(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public List getAllowIps() {
return allowIps;
}
public void setAllowIps(List allowIps) {
this.allowIps = allowIps;
}
}
然后我们实现dubbo的Filter接口:
/**
* Created by j.tommy on 2017/11/4.
*/
public class IPWhiteListFilter implements Filter{
private IPWhiteList ipWhiteList;
public void setIpWhiteList(IPWhiteList ipWhiteList) {
this.ipWhiteList = ipWhiteList;
}
@Override
public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {
if (!ipWhiteList.isEnabled()) {
System.out.println("dubbo IP白名单被禁用!");
return invoker.invoke(invocation);
}
String clientIp = RpcContext.getContext().getRemoteHost();
if (ipWhiteList.getAllowIps().contains(clientIp)) {
return invoker.invoke(invocation);
}
System.out.println("dubbo客户端IP[" + clientIp + "]不在白名单,禁止调用!");
return new RpcResult();
}
}
在/resources目录下,新建META-INF/dubbo目录,并新建一个名为com.alibaba.dubbo.rpc.Filter的文本文件 ,内容如下:
ipWhiteListFilter=com.tommy.service.provider.filter.IPWhiteListFilter
dubbo的配置文件中增加filter的配置:
127.0.0.1
192.168.71.170