第一個Dubbo Filter

概述

在Dubbo的整體設計中,Filter是一個很重要的概念,包括Dubbo本身的大多數功能,都是基於此擴展點實現的,在每次的調用過程中,Filter的攔截都會被執行。

Dubbo Filter的加載機制

Dubbo中已經實現的Filter大概有二十幾個,它們的入口都是ProtocolFilterWrapper,ProtocolFilterWrapper對Protocol做了Wrapper,會在加載擴展的時候被加載進來,下面我們來看下這個Filter鏈是如何構造的。

//ProtocolFilterWrapper.java
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        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;
        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;
    }

Dubbo Filter的激活機制

通過上述代碼我們可以看到,在buildInvokerChain中,先獲取所有已經激活的調用鏈,這裏的調用鏈是已經排好序的。再通過Invoker來構造出一個Filter的調用鏈,最後構建出的調用鏈大致可以表示爲:Filter1->Filter2->Filter3->......->Invoker,下面我們來看一下,第一步中獲取已經激活的調用鏈的詳細流程:

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>();
        
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);

        if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) {
                    T ext = getExtension(name);
                    if (! names.contains(name) && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name) 
                            && isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i ++) {
            String name = names.get(i);
            if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                    && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
                           if (Constants.DEFAULT_KEY.equals(name)) {
                    if (usrs.size() > 0) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        if (usrs.size() > 0) {
            exts.addAll(usrs);
        }
        return exts;
    }

通過以上代碼可以看到,用戶自己配置的Filter中,有些是默認激活,有些是需要通過配置文件來激活。而所有Filter的加載順序,也是先處理Dubbo的默認Filter,再來處理用戶自己定義並且配置的Filter。通過"-"配置,可以替換掉Dubbo的原生Filter,通過這樣的設計,可以靈活地替換或者修改Filter的加載順序。

Dubbo原生的Filter

Dubbo原生的Filter很多,RpcContext,accesslog等功能都可以通過Dubbo來實現,下面我們來介紹一下Consumer端用於上下文傳遞的ConsumerContextFilter:

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        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對象將客戶端設置的attachments參數傳遞到服務端。並且在調用完成後清除這些參數,這就是爲什麼請求狀態信息可以按次記錄並且進行傳遞。

實現一個Dubbo Filter

得益於Dubbo靈活的設計和良好的可擴展性,我們可以通過實現自己的Dubbo Filter來完成調用鏈路中的邏輯嵌入,比如,耗時統計,monitor信息統計等,下面我們來實現一個簡單的Filter:

Maven 項目結構:

src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxFilter.java (實現Filter接口)
    |-resources
        |-META-INF
            |-dubbo
                |-com.alibaba.dubbo.rpc.Filter (純文本文件,內容爲:xxx=com.xxx.XxxFilter)

XxxFilter.java:

public class XxxFilter implements Filter {
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // before filter ...
        Result result = invoker.invoke(invocation);
        // after filter ...
        return result;
    }
}

注:invoker:指服務調用者、請求者;invocation:指服務rpc調用相關參數信息。

 

META-INF/dubbo/com.alibaba.dubbo.rpc.Filter:

xxx=com.xxx.XxxFilter

在 xml 中配置:

<!-- 消費方調用過程攔截 -->
<dubbo:reference filter="xxx" />
<!-- 消費方調用過程缺省攔截器,將攔截所有reference -->
<dubbo:consumer filter="xxx"/>
<!-- 提供方調用過程攔截 -->
<dubbo:service filter="xxx" />
<!-- 提供方調用過程缺省攔截器,將攔截所有service -->
<dubbo:provider filter="xxx"/>

或者使用註解:

@Activate(group = "consumer")
public class XxxFilter implements Filter {
    // ...
}

使用 xml 的配置方式會更加靈活,粒度更細。

在before和after中,可以實現自己的業務邏輯來賦予該filter一定的功能。編寫和配置完成後,該filter就會被Dubbo框架激活並且在調用鏈中執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章