1. 程式人生 > >dubbo自定義filter,報No such extension xxxFilter for filter/com.alibaba.dubbo.rpc.Filter錯誤

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。