dubbo自定義filter,報No such extension xxxFilter for filter/com.alibaba.dubbo.rpc.Filter錯誤
前言
最近在學習dubbo的filter時候,根據dubbo的開發手冊,自定義了一個filter,然後配置,結果控制檯報 No such extension xxxFilter for filter/com.alibaba.dubbo.rpc.Filter錯誤。特此記錄。
故障復現
首先定義filter,需要實現com.alibaba.dubbo.rpc.Filter介面
/** * All rights Reserved, Designed By www.tydic.com * @Title: ExporterListener.java * @Package com.andy.exporterListener * @Description: TODO(用一句話描述該檔案做什麼) * @author: andyzhu * @date: 2018年11月7日 下午9:50:50 * @version V1.0 * @Copyright: 2018 www.acc.com Inc. All rights reserved. * 注意:禁止外洩以及用於其他的商業目 */ package com.andy.exporterfilter; import com.alibaba.dubbo.common.logger.Logger; import com.alibaba.dubbo.common.logger.LoggerFactory; import com.alibaba.dubbo.rpc.Exporter; import com.alibaba.dubbo.rpc.Filter; import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcException; /** * @author sks,這個andyfilter就是簡單的驗證filter功能 * */ public class AndyFilter implements Filter{ private static Logger log = LoggerFactory .getLogger(AndyFilter.class); /* (non-Javadoc) * @see com.alibaba.dubbo.rpc.Filter#invoke(com.alibaba.dubbo.rpc.Invoker, com.alibaba.dubbo.rpc.Invocation) */ @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { // TODO Auto-generated method stub log.info(("自定義過濾器--》"+invoker.getInterface())); return invoker.invoke(invocation); } }
對上述filter進行配置(專案使用maven進行構建)
在resources目錄下新建META-INF資料夾,然後建立子資料夾dubbo,最後新建檔案com.alibaba.dubbo.rpc.Filter,檔案內容為
andyFilter=com.andy.exporterfilter.AndyFilter
在配置檔案中使用這個filter
<dubbo:service retries="0" interface="cn.andy.dubbo.DataService" ref="dataServiceImpl"filter="andyFilter" />
大功告成,但是在啟動工程時候失敗,報如下錯誤:
2018-11-12 19:38:52,038 WARN [AbstractApplicationContext.java:546] : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.andy.dubbo.DataService': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are: PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'filter' threw exception; nested exception is java.lang.IllegalStateException: No such extension andyFilter for filter/com.alibaba.dubbo.rpc.Filter 2018-11-12 19:38:52,040 ERROR [DubboProviderMain.java:38] : == DubboProvider context start error: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cn.andy.dubbo.DataService': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are: PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'filter' threw exception; nested exception is java.lang.IllegalStateException: No such extension andyFilter for filter/com.alibaba.dubbo.rpc.Filter
大意就是找不到這個andyFilter。
原因分析
根據錯誤提示,大概率原因是找不到andyFilter。根據dubbo的spi擴充套件機制原理。這個andyFilter是在進行filter介面的擴充套件載入實現時候匯入進來的,那麼我們就從那裡分析。
所有的介面的spi擴充套件都從下面這個方法得到相應的介面實現。我們在這個方法裡打上斷點,當發現ExtensionLoader的type是com.alibaba.dubbo.rpc.Filter時候(即開始載入filter的介面的相關的所有擴充套件實現),在路徑是META-INF/dubbo/com.alibaba.dubbo.rpc.Filter時候時(我們自定義的AndyFilter就在這個目錄下的檔案中),下面的方法中 urls = classLoader.getResources(fileName);得到的結果為空,即在這個目錄下找不到com.alibaba.dubbo.rpc.Filter這個檔案。
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class<?> clazz = Class.forName(line, true, classLoader);
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
if(cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (! cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
} else {
try {
clazz.getConstructor(type);
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (! cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}
既然找不到這個檔案,那麼我們就直接進入編譯後的目錄下,取找找是否真的沒有這個檔案。
果然,連dubbo這個資料夾都沒有,更別說com.alibaba.dubbo.rpc.Filter這個檔案了。
那麼問題出在哪呢?
考慮一會,大概率是在使用maven構建工程時候,出了問題。
翻看maven關於resources的配置
<resources>
<resource>
<targetPath>${project.build.directory}/classes</targetPath>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
果然,maven在進行相關的resoures的資原始檔配置時,只包含了字尾名是xml和properities的檔案,其他的檔案都給過濾了!而我們的檔名沒有後綴名(或者可以理解為字尾名就是Filter).
找到了問題,那就把所有的字尾名的檔案都給放到編譯後的resources資料夾。
<resources>
<resource>
<targetPath>${project.build.directory}/classes</targetPath>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.*</include>
</includes>
</resource>
,重新進行maven clean,和maven install。然後執行:沒問題啦!
filter的原始碼分析
filter的執行在ProtocolFilterWrapper類中。
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
public ProtocolFilterWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
}
ProtocolFilterWrapper 實現了Protocol 介面,而且其構造方法中,有Protocol 這個引數。說明其是一個wrapper包裝類。我們以dubboProtocol為例。在例項化dubboProtocol時候,,會對Protocol先進行依賴注入,然後進行Wrapper包裝,最後返回被修改過的Protocol。比如,dubboprotocol包裝經過了ProtocolFilterWrapper,ProtocolListenerWrapper。這裡先不考慮ProtocolListenerWrapper。
那麼,這個類在執行export的方法會執行什麼邏輯呢?
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//當protocol是registerProtocol時候,直接執行,不經過filter
if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
return protocol.export(invoker);
}
//重點是buildInvokerChain方法。
return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}
重點是buildInvokerChain方法。這個方法相當於在釋出(或者獲取)任務時候,會先經過這些過濾器,完成過濾器操作後,再執行真正的方法。
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;
}
假設有一個待發布的invokerA,和兩個過濾器
根據上面的buildInvokerChain方法,執行for迴圈操作。
因此 return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER))方法中的invoker就是釋出的invokerX。