Dubbo-Filter机制概述

1.Filter介绍

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,其注解含义如下:

  • group: 所属组,String[],例如消费端、服务端。
  • value String[],如果指定该值,只有当消费者或服务提供者URL中包含属性名为value的键值对,该过滤器才处于激活状态。
  • before:String[],用于指定执行顺序,before指定的过滤器在该过滤器之前执行。
  • after:string[],用于指定执行顺序,after指定的过滤器在该过滤器之后执行。
  • order:用户指定顺序,值越小,越先执行。

除了支持默认的过滤器外,Dubbo还支持自定义Filter,可以通过service.filter指定过滤器,多个用英文逗号隔开,其配置方法为:

<dubbo:service ……> 
    <dubbo:parameter key = “service.filter” value = “filter1,filer2,…”/> 
</dubbo:service> 

当然,可以为所有服务提供者设置共用过滤器,其指定方法为:

<dubbo:provider …> 
    <dubbo:parameter key = “service.filter” value = “filter1,filer2,…”/>
</dubbo:provider> 

消费端自定义过滤器的key为reference.filter,其使用方法在< dubbo:reference/>标签或< dubbo:consumer/>标签下定义属性。

2.创建分析

调用链的构建是通过下面的方法来实现:

com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper.buildInvokerChain(Invoker<T>, String, String)

该方法源码如下:

 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.size() > 0) {
            for (int i = filters.size() - 1; i >= 0; i --) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                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列表,然后创建与Fitler数量一样多Invoker结点,接着将这些结点串联在一起,构成一个链表,最后将这个链的首结点返回,随后的调用中,将从首结点开始,依次调用各个结点,完成调用后沿调用链返回。这里各个Invoker结点的串联是通过与其关联的invoke方法来完成的。接下来分析这个调用链的创建。

buildInvokerChain(final Invoker<T> invoker, String key, String group)

调用buildInvokerChain时会传入invoker参数:

Invoker<T> last = invoker; 

通过创建last,来指向头结点。初始状态如下图所示:

接着通过循环遍历获取到的Filter,同时创建Invoker结点,每个结点对应一个Filter。此时循环内部定义了next指针。

final Invoker<T> next = last;

该指针每次更新为指向原链表中的头结点,即last指针。 随后创建新结点,并更新last。此时新结点将作为链表中的头结点。

final Invoker<T> next = last;
last = new Invoker<T>(),

结果如下图所示。

接着通过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)的一个实现。这样系统中增加一个新的过滤器预处理请求时,无须修改原有系统的代码,只需重新建调用链即可。

3.自定义Filter

以为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<String> allowIps; // 允许的白名单列表
    public boolean isEnabled() {
        return isEnabled;
    }
    public void setEnabled(boolean isEnabled) {
        this.isEnabled = isEnabled;
    }
    public List<String> getAllowIps() {
        return allowIps;
    }
    public void setAllowIps(List<String> 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的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    <dubbo:application name="demo-provider"/>
    <dubbo:registry address="multicast://224.5.6.7:1234"/>
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:provider filter="ipWhiteListFilter"/>
    <dubbo:service interface="com.tommy.service.DemoService" ref="demoService"/>
    <bean id="demoService" class="com.tommy.service.provider.DemoServiceImpl"/>
    <bean id="ipWhiteList" class="com.tommy.service.provider.bean.IPWhiteList">
        <property name="enabled" value="true"/>
        <property name="allowIps">
            <list>
                <value>127.0.0.1</value>
                <value>192.168.71.170</value>
            </list>
        </property>
    </bean>
</beans>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值