前言
最近在学习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。