完整的代碼及:java
github: https://github.com/Athlizo/spring-dubbo-parentgit
碼雲: https://git.oschina.net/null_584_3382/spring-dubbo-parentgithub
dubbo框架中,提供了多種擴展,好比Dubbo的過濾器擴展,路由擴展等等。而且dubbo已經提供了擴展的一些默認實現。本篇文章主要介紹:1)dubbo擴展原理,2)經過簡單的改造,使dubbo讓擴展的使用更方便。spring
以攔截器做爲例子說明,引用dubbo官方文檔中的一個圖。app
<!-- 在xml配置文件中設置 --> <dubbo:reference filter="xxx,yyy" /> <!-- 消費方調用過程攔截 --> <dubbo:consumer filter="xxx,yyy"/> <!-- 消費方調用過程缺省攔截器,將攔截全部reference --> <dubbo:service filter="xxx,yyy" /> <!-- 提供方調用過程攔截 --> <dubbo:provider filter="xxx,yyy"/> <!-- 提供方調用過程缺省攔截器,將攔截全部service -->
dubbo擴展配置文件 src |-main |-java |-com |-xxx |-XxxFilter.java (實現Filter接口) |-resources |-META-INF |-dubbo |-com.alibaba.dubbo.rpc.Filter (純文本文件,內容爲:xxx=com.xxx.XxxFilter)
//擴展類 package com.xxx; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcException; 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; } }
import com.alibaba.dubbo.common.extension.Activate; import com.alibaba.dubbo.rpc.Filter; @Activate(group = "provider", value = "xxx") // 只對提供方激活,group可選"provider"或"consumer" public class XxxFilter implements Filter { // ... }
經過以上3步就能夠自定義一個Filter框架
這個類是用戶管理全部的dubbo擴展,是一個泛型,根據不一樣的擴展類(例如Filter,Protocol等),保存該類擴展的全部實現類。ide
例如,要想獲取每一個具體的ExtensionLoader,使用getExtensionLoader(Class) 來獲取,若是沒有就建立,若是以前建立過就返回以前建立的,邏輯比較簡單。ui
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { ... //省略參數校驗 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { //EXTENSION_LOADERS用於保存ExtensionLoader全部泛型實現子類 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
獲取有效的擴展類,是經過getActivateExtension方法。代碼以下:this
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(); //加載@Activate註解的配置的擴展 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>(); //加載 經過values傳遞過來的指定擴展 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; }
傳入的參數說明:url
其中有一個比較重要的方法,getExtensionClasses(),原理就是初始化擴展名字及其對應的Class,在往裏面看
private Map<String, Class<?>> getExtensionClasses() { Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
其中loadExtensionClasses方法以下:這裏看到了,爲何要配置在自定義過濾器的時候咱們須要配置META-INFO.dubbo.com.alibaba.dubbo.rpc.Filter這個文件,原來就是在這裏經過文件加載到ExtensionLoad中去的,而且路徑寫死在這裏。
private Map<String, Class<?>> loadExtensionClasses() { ... Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); // META-INF/dubbo/internal/ loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); // META-INF/dubbo/ loadFile(extensionClasses, DUBBO_DIRECTORY); // META-INF/services/ loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
仍是以Filter爲例子,在哪裏調用Filter呢?首先要明白,dubbo的遠程調用都是經過抽象接口Invoker爲核心。ProtocolFilterWrapper的類中的buildInvokerChain方法用來建立Invoder的調用鏈。核心代碼以下:
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 Result invoke(Invocation invocation) throws RpcException { return filter.invoke(next, invocation); } ... }; } } return last; }
這裏就使用到了ExtensionLoader的getActivateExtension方法獲取當前有效的Filter。
在 https://my.oschina.net/u/3039671/blog/856577 文章中介紹了怎麼使用Spring boot來加載dubbo的相關bean,那麼就會想,對於dubbo的Filter,有沒有更優雅的聲明方式?例如Spring MVC中的聲明一個Filter就是直接聲明一個普通的Bean同樣。
仍是以Filter爲例子(注意,下面分析Filter是針對全局的Filter,即Provider或者Consumer層面Filter)
首先,擴展是從ExtensionLoad<Filter>中獲取,那麼咱們的目的就是在ExtensionLoad<Filter>中加入本身的Filter。上面說過,ExtensionLoad加載擴展有2種方式,一種是經過參數中Values來獲取,另一種是經過@Activate註解。簡單分析一下:
這兩種方式都能達成咱們的需求,可是從開發難度來講使用@Activate註解相對簡單,並且第二點能夠經過其餘方式(proxy代理)來解決。所以下下面介紹基於@Activate註解來快速建立一個Dubbo的Filter。
自定義一個BeanPostProcessor,對每個Bean作如下處理:
3.2 完成之後的效果
這樣就能夠快速建立一個Dubbo Filter
@Bean ProviderFilter providerFilter(){ return new ProviderFilter(); } static class ProviderFilter extends AbstractDubboProviderFilterSupport { public Result invoke(Invoker<?> invoker, Invocation invocation) { System.out.println("ProviderFilter"); return invoker.invoke(invocation); } }
若是有跟定製化的需求,可使用@Activate註解。
@Bean CustomFilter customFilter(){ return new CustomFilter(); } @Activate(group = Constants.PROVIDER) static class CustomFilter extends AbstractDubboFilterSupport { public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { System.out.println("CustomFilter"); return invoker.invoke(invocation); } public Filter getDefaultExtension() { return this; } }
注意,工程中的AnnotationBeanPostProcessor的實際上是對Dubbo中的AnnotationBean的修改,除了掃描Dubbo的@Service的註解方式修改了之外,主要是對遠程代理延時建立的邏輯。不然在@Reference建立代理的時候,咱們的Filter還沒建立,天然Filter就不能加入到Invoker調用鏈中。