dubbo的spi機制

SPI

SPI是一種擴展機制,在java中SPI機制被普遍應用,好比Spring中的SpringServletContainerInitializer 使得容器啓動的時候SpringServletContainerInitializer 執行onStartup方法。在dubbo中,dubbo實現了本身的spi擴展機制,下面詳細的講解下,dubbo的擴展機制。java

dubbo SPI使用-Filter的定義使用

 dubbo中的fiter是使用spi機制實現的典型應用,如今使用filter做爲例子,展現先dubbo的spi機制。git

首先定義一個filter。github

public class MyFilter implements Filter {

  public Result invoke(Invoker<?> invoker, Invocation invocation)
      throws RpcException {
    System.out.println("開始調用 MyFilter");
    Result result = invoker.invoke(invocation);
    System.out.println("結束調用 MyFilter");
    return result;
  }
}

將filter加到dubbo默認的filter服務器

@Activate(group = {Constants.CONSUMER})

在消費端使用filter

在消費者的resource文件下建app

/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filteride

內容爲:源碼分析

myFilter=com.lenny.sample.dubbo.common.filter.MyFilter

在消費端指定filterui

@Reference(version = "1.0.0",filter = "myFilter")
private UserDubboService userDubboService;

在調用userDubboService的服務器時在控制檯打印以下內容:url

控制檯打印

在服務端使用filter

在服務端的resource文件下建.net

/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter

內容爲:

myFilter=com.lenny.sample.dubbo.common.filter.MyFilter

在服務提供者指定filter:

@Service(version = "1.0.0",filter = "myFilter")

一樣在控制檯打印以下內容:

控制檯打印

dubbo默認filter

dubbo默認提供一些filter。

dubbo默認fiter定義

dubbo filter源碼分析

Filter的加載源碼位於ProtocolFilterWrapper類,具體實現過程

// 服務端暴露
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
        return protocol.export(invoker);
    }
    return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
}

// 消費端
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        return protocol.refer(type, url);
    }
    return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}

主要看buildInvokerChain。

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
    // 獲取所有的filter
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
        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 {
                        // 執行filter的invoke方法
                        return filter.invoke(next, invocation);
                    }

                    @Override
                    public void destroy() {
                        invoker.destroy();
                    }

                    @Override
                    public String toString() {
                        return invoker.toString();
                    }
                };
            }
        }
        return last;
    }

在執行export的時候獲取所有的filter,綁定到要執行的方法上面,在要執行的方面的時候,執行filter方法。

Spi的加載原理

在ExtensionLoader中有loadExtensionClasses方法,加載所有的擴展類。

private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

private Map<String, Class<?>> loadExtensionClasses() {
     final SPI defaultAnnotation = type.getAnnotation(SPI.class);
     if (defaultAnnotation != null) {
         String value = defaultAnnotation.value();
         if ((value = value.trim()).length() > 0) {
             String[] names = NAME_SEPARATOR.split(value);
             if (names.length > 1) {
                 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                                                 + ": " + Arrays.toString(names));
             }
             if (names.length == 1) cachedDefaultName = names[0];
         }
     }

     Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
     // 去相應的文件路徑下掃描文件
     loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
     loadDirectory(extensionClasses, DUBBO_DIRECTORY);
     loadDirectory(extensionClasses, SERVICES_DIRECTORY);
     return extensionClasses;
 }
private void loadDirectory(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 resourceURL = urls.nextElement();
                // 加載資源文件
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                     type + ", description file: " + fileName + ").", t);
    }
}

根據使用@SPI的註解的類全類名去以下路徑,找到相應問文件,解析文件內容

  • META-INF/dubbo/internal/全類名
  • META-INF/dubbo/全類名
  • META-INF/services/全類名

如以filter爲例:

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface Filter {
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}

Filter的全類名是com.alibaba.dubbo.rpc.Filter。因此能夠去掃描以下路徑

  • META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter

  • META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
  • META-INF/services/com.alibaba.dubbo.rpc.Filter

在上面的例子中將MyFilter的文件定義到了META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter路徑下。

固然也能夠放到META-INF/dubbo/com.alibaba.dubbo.rpc.Filter和META-INF/services/com.alibaba.dubbo.rpc.Filter。

如圖:放到下面三個位置任何一個位置都是能夠的。

filter定義文件位置

本文源代碼:https://github.com/applenele/sample

相關文章
相關標籤/搜索