Filter 機制也稱攔截器機制,在衆多框架或者語言中很常見,可以實現登錄鑑權,網關攔截、封裝全局狀態返回等,博主文章以下幾個問題展開:
- Filter 的例子
- Dubbo中內置的Filter是怎樣的?Consumer 和Provider 默認使用的 Filter 有哪些?
- Filter 何時初始化?
- Filter 何時會被調用?
- Filter 中 ListenableFilter 有何作用?
- Consumer 和Provider 如何使用Dubbo 內置Filter?
- 從Filter 看 自定義SPI 在哪些地方會被使用
以上問題看完文章後相信大家就可以清楚,若有疑問,關注博主公衆號:六點A君,回覆標題獲取最新答案><
Filter 是Dubbo 的一個擴展點,可以理解爲攔截作用,Dubbo 本身大多功能都基於該擴展點實現。
Filter 例子
先說說Dubbo 中Filter 的使用,以下摘抄自Dubbo官網:
- 用戶自定義 filter 默認在內置 filter 之後。
- 特殊值
default
,表示缺省擴展點插入的位置。比如:filter="xxx,default,yyy"
,表示xxx
在缺省filter
之前,yyy
在缺省filter
之後。 - 特殊符號
-
,表示剔除。比如:filter="-foo1"
,剔除添加缺省擴展點foo1
。比如:filter="-default"
,剔除添加所有缺省擴展點。 provider
和service
同時配置的filter
時,累加所有filter
,而不是覆蓋。比如:<dubbo:provider filter="xxx,yyy"/>
和<dubbo:service filter="aaa,bbb" />
,則xxx,yyy,aaa,bbb
均會生效。如果要覆蓋,需配置:<dubbo:service filter="-xxx,-yyy,aaa,bbb" />
Dubbo
文檔:http://dubbo.apache.org/zh-cn/docs/dev/impls/filter.html
Dubbo
中使用 Filter
有兩種途徑,可以使用Dubbo
內置的Filter
,或者可以使用自定義的Filter
。
以自定義Filter 爲例,其中帶入
- 定義類實現
org.apache.dubbo.rpc.Filter
- 在
META-INF/dubbo
下增加 增加 SPI 文件org.apache.dubbo.rpc.Filter
,填寫SPI 映射,例如traceConsumer=com.anla.rpc.filter.consumer.filter.ConsumerTraceFilter
- 在 Consumer 端 的
dubbo:consumer
或dubbo:reference
進行聲明,例如<dubbo:consumer filter="traceConsumer"/>
,同理可以在 Provider 端的dubbo:provider
或dubbo:service
進行聲明。
當然在這一步可以使用 Dubbo內置的Filter 進行使用。也可以通過-
(減號) 進行內置Filter 刪除。
具體Filter 例子可以看博主寫的例子:https://github.com/anLA7856/dubbolearn/tree/master/filter
Dubbo 內置的Filter
如何找Dubbo內置Filter呢?
由於Dubbo 提供SPI 機制,所以我們可以沿着這條路往下找,即找到 `org.apache.dubbo.rpc.Filter`` 在找到其有哪些子類就可以了。
上面圖片看起來過於眼花繚亂,下面博主整理表格或許來的更加直觀(起始下面更眼花繚亂,各位看官直接過了就好,需要再來細看)
類名 | @Adaptive | 是否默認Consumer | 是否默認Provider | 排序 | 作用 |
---|---|---|---|---|---|
ClassLoaderFilter |
@Activate(group = CommonConstants.PROVIDER, order = -30000) |
否 | 是,默認存在 | -30000 | 用於添加invoker的ClassLoader 到本地線程中 |
DeprecatedFilter |
@Activate(group = CommonConstants.CONSUMER, value = DEPRECATED_KEY) |
是,需要指定 值爲 deprecated | 無 | 無 | 用於提醒錯誤如果接口配置了deprecated,則會打印錯誤 |
EchoFilter |
@Activate(group = CommonConstants.PROVIDER, order = -110000) |
否 | 是,默認有 | -110000 | 用於在服務端提供基於EchoService的功能鏈路相應功能 |
MetricsFilter |
無 | 否 | 否 | 無 | 用於一些統計接口監控統計,提供report 功能 |
TpsLimitFilter |
@Activate(group = CommonConstants.PROVIDER, value = TPS_LIMIT_RATE_KEY) |
否 | 是,需要指定,值爲tpc | 無 | 限流作用,用於限制tps,用戶可自行配置 |
AccessLogFilter |
@Activate(group = PROVIDER, value = ACCESS_LOG_KEY) |
否 | 是,需要指定,值爲accesslog | 無 | 用於打印接口log |
ValidationFilter |
@Activate(group = {CONSUMER, PROVIDER}, value = VALIDATION_KEY, order = 10000) |
是,需要指定 | 是,需要指定 | 10000 | 在配置完validation後,在具體調用前既可以使用,用戶可以基於org.apache.dubbo.validation.Validation 的 SPI方式實現新Validation |
DubboAppContextFilter |
@Activate(group = "consumer") |
是,默認有 | 否 | 無 | 用於將當前Conusmer 的applicationName 放入attachment中 |
CacheFilter |
@Activate(group = {CONSUMER, PROVIDER}, value = CACHE_KEY) |
是,需要指定,值爲cache | 是,需要指定,值爲cache | 無 | 爲Dubbo的核心組件,支持 service,method,consumer or provider 四種粒度緩存 |
TokenFilter |
@Activate(group = CommonConstants.PROVIDER, value = TOKEN_KEY) |
否 | 有,需要指定,value爲token | 無 | 用於鑑權訪問 |
TraceFilter |
@Activate(group = CommonConstants.PROVIDER) |
否 | 有,默認存在 | 無 | 用於鏈路跟蹤,放入相應信息 |
SentinelDubboProviderFilter |
@Activate(group = "provider") |
否 | 僅Provder,默認有 | 無 | 爲Sentinel 提供的 服務端Filter |
GenericImplFilter |
@Activate(group = CommonConstants.CONSUMER, value = GENERIC_KEY, order = 20000) |
是,需要指定,值爲generic | 否 | 20000 | 用於支持泛化generic 調用 |
MonitorFilter |
@Activate(group = {PROVIDER, CONSUMER}) |
是,默認有 | 是,默認有 | 無 | 用於監控接口調用 |
ContextFilter |
@Activate(group = PROVIDER, order = -10000) |
否 | 是,默認有 | -10000 | 在invoker中設置服務端的RpcContext |
ExceptionFilter |
@Activate(group = CommonConstants.PROVIDER) |
否 | 是,默認有 | 無 | unexpect 異常將會在provider 中error層級記錄 |
CompatibleFilter |
無 | 否 | 否 | 無 | 爲版本兼容所有,讓rpc調用返回值可以兼容舊版本的invoker 所需 |
GenericFilter |
@Activate(group = CommonConstants.PROVIDER, order = -20000) |
無 | 有,默認有 | -20000 | 用於實現和 generic 泛化調用相關邏輯 |
FutureFilter |
@Activate(group = CommonConstants.CONSUMER) |
有,默認有 | 無 | 無 | 屬於一個事件的Filter,待研究 |
TimeoutFilter |
@Activate(group = CommonConstants.PROVIDER) |
無 | 有 | 無 | 記錄 超時的 invocation,但是不阻止該invoker 運行,也就是會照常運行 |
ActiveLimitFilter |
@Activate(group = CONSUMER, value = ACTIVES_KEY) |
有,需要指定 | 無 | 無 | 用於限制 client 端的調用量 |
ConsumerContextFilter |
@Activate(group = CONSUMER, order = -10000) |
有,默認有 | 無 | -10000 | 用於設定RpcContext中一些屬性,例如invoker,invocation等。 |
ExecuteLimitFilter |
@Activate(group = CommonConstants.PROVIDER, value = EXECUTES_KEY) |
無 | 有,需要指定 executes | 無 | 用於限制,單method 下 ,最大並行請求數限制 |
從上面分析來看,當程序沒有顯示指定Filter,那麼Consumer 和 Provider 存在的Filter 如下:
Provider | Consumer |
---|---|
EchoFilter ,ClassLoaderFilter , GenericFilter , ContextFilter , TraceFilter , TimeoutFilter , MonitorFilter , ExceptionFilter ,SentinelDubboProviderFilter |
ConsumerContextFilter , FutureFilter ,MonitorFilter , DubboAppContextFilter |
以上得出 Consumer
和 Provider
可以由 上面表格推到出,一方面也可以看到 @Activate
註解用途:
- 如果在
@Adaptive
有指定 value,則需要配置才能被加載。 - 如果沒有
@Adaptive
,則無法被加載 - Filter 中 排序按照 order 來的,倒序排。
- 最後有刪除的
SentinelDubboProviderFilter
實際上不屬於Dubbo 的包,它的@Adaptive
註解本應被加載,但是如果沒有引入相應starter 文件,不回被加載。。只有當被用到時,纔會被加載,纔會 sentinel 的 SPI 文件纔會被加載,纔會被加入到默認的SPI 中。 - 所以在編寫代碼是,如果使用了
@Adaptive
註解,滿足條件後,即使不配置filter
,也會生效,就是這個道理。
Filter 何時初始化
Filter 初始化是伴隨着 ProtocolFilterWrapper
使用而初始化。而通過前面文章可知,ProtocolFilterWrapper
作爲包裝類,當有Protocol
初始化,它就會初始化,並且由鏈路調用一層一層下去。
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
if (REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}
當 Provider 暴露服務會調用 export
方法,而 Consumer 則會調用refer 方法,上述方法都做了以下同樣的事:
- 首先初始化註冊中心協議
- 調用
buildInvokerChain
構造Filter
鏈。
下面看看 buildInvokerChain
:
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
// 獲取 group 和key下所有的 Filter 的 SPI 類文件
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
// 構建Filter 鏈
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
// 直接執行filter 的invoke
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
// onError callback
if (filter instanceof ListenableFilter) {
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
listener.onError(e, invoker, invocation);
}
}
throw e;
}
return asyncResult;
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return new CallbackRegistrationInvoker<>(last, filters);
}
上面方法看起來也容易理解:
- 通過SPI 中,加載 Filter 的擴展類。具體加載規則可以看博主前面文章以及上文表格分析
- 將每個Filter鏈起來,即包裝每一個Filter。這樣當有請求時候,就會依次執行每一個鏈起來的Filter
- 最後返回
CallbackRegistrationInvoker
的封裝對象
在 Filter
中有個特殊的子類:ListenableFilter
:
public abstract class ListenableFilter implements Filter {
protected Listener listener = null;
public Listener listener() {
return listener;
}
}
而 Listener
則 爲 Filter
中的內部接口,依附 Filter
存在:
interface Listener {
// 當結果返回時調用
void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);
// 當調用方出現異常時調用
void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
}
有部分Filter
是 實現自 Listener
,故其有監聽作用。
在 buildInvokerChain
中綁定 invoke
鏈時,則使用了 onError
,當Filter
出錯時,則會記錄並調用 onError
方法:
try {
asyncResult = filter.invoke(next, invocation);
} catch (Exception e) {
// onError callback
if (filter instanceof ListenableFilter) {
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
listener.onError(e, invoker, invocation);
}
}
throw e;
}
而再看 buildInvokerChain
返回的 CallbackRegistrationInvoker
時,在其invoke方法中可以看到,在 Filter鏈 中最後一個 調用 完之後,則會進行異步封裝:
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 異步封裝
Result asyncResult = filterInvoker.invoke(invocation);
asyncResult.thenApplyWithContext(r -> {
for (int i = filters.size() - 1; i >= 0; i--) {
Filter filter = filters.get(i);
// 告知 ListenableFilter 的子類
if (filter instanceof ListenableFilter) {
Filter.Listener listener = ((ListenableFilter) filter).listener();
if (listener != null) {
listener.onResponse(r, filterInvoker, invocation);
}
} else {
filter.onResponse(r, filterInvoker, invocation);
}
}
return r;
});
return asyncResult;
}
並且對 asyncResult
異步調用完結果進行監聽,並且循環filter,依次調用他們的onResponse
方法。
如何 定位 SPI 的使用點
本篇以Filter 這個SPI 類型 爲例,並且分析了 @Adaptive
註解 對於加載SPI 文件區別。因而總結下
SPI 使用點:
- 查找其基接口,看何處被
ExtensionLoader
加載 - 分析
ExtensionLoader
中使用 什麼方法加載初 其相關聯的SPI文件,方法可能爲:getActivateExtension
,getActivateExtension
,getExtension
…,不同方法加載出的SPI擴展類是不一樣的,都是圍繞 SPI配置文件,@Adaptive註解,@SPI 註解進行。 - 獲取完具體SPI擴展類之後,就是對類的操作了,這個要看具體邏輯代碼了。
覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路: