Dubbo的SPI機制與JDK機制的不同及原理分析

来源:https://www.cnblogs.com/maratong/archive/2020/02/19/12333717.html
-Advertisement-
Play Games

從今天開始,將會逐步介紹關於DUbbo的有關知識。首先先簡單介紹一下DUbbo的整體概述。 概述 Dubbo是SOA(面向服務架構)服務治理方案的核心框架。用於分散式調用,其重點在於分散式的治理。 簡單的來說,可以把它分為四個角色。服務提供方(Provider)、服務消費方(Consumer)、註冊 ...


從今天開始,將會逐步介紹關於DUbbo的有關知識。首先先簡單介紹一下DUbbo的整體概述。

概述

Dubbo是SOA(面向服務架構)服務治理方案的核心框架。用於分散式調用,其重點在於分散式的治理。
簡單的來說,可以把它分為四個角色。服務提供方(Provider)、服務消費方(Consumer)、註冊中心和監控中心。通過註冊中心對服務進行註冊和訂閱,通過監控中心對服務進行監控。
核心功能

  • Remoting:遠程通訊,提供對多種NIO框架抽象封裝,包括“同步轉非同步”和“請求-響應”模式的信息交換方式。
  • Cluster: 服務框架,提供基於介面方法的透明遠程過程調用,包括多協議支持,以及軟負載均衡,失敗容錯,地址路由,動態配置等集群支持。
  • Registry: 服務註冊,基於註冊中心目錄服務,使服務消費方能動態的查找服務提供方,使地址透明,使服務提供方可以平滑增加或減少機器。

Dubbo組件角色

Provider: 暴露服務的服務提供方
Consumer: 調用遠程服務的服務消費方
Registry: 服務註冊與發現的註冊中心
Monitor: 統計服務的調用次數和調用時間的監控中心
Container: 服務運行容器,常見的容器有Spring容器

調用關係:

  1. 服務容器負責啟動,載入,運行服務提供者
  2. 服務提供者在啟動時,向註冊中心註冊自己提供的服務。
  3. 服務消費者在啟動時,向註冊中心訂閱自己所需的服務。
  4. 註冊中心返回服務提供者地址列表消費者,如果有變更,註冊中心將基於長連接推送變更數據給消費者。
  5. 服務消費者,從提供者地址列表中,基於軟負載均衡演算法,選一臺提供者進行調用,如果調用失敗,再選另一臺調用。
  6. 服務消費者和提供者,在記憶體中累計調用次數和調用時間,定時每分鐘發送一次統計數據到監控中心Monitor。
    1.png

SPI(Service Provider Interfaces)

它是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。在JDK文檔中,它這樣解釋道:

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service.

在面向對象的設計裡面,模塊之間推薦是基於介面編程,而不是對實現類進行硬編碼,這樣做也是為了模塊設計的可拔插原則。為了在模塊裝配的時候不再程式里指明是那個實現,就需要一種服務發現的機制,jDK的SPI就是為某個介面尋找服務實現。
2.png

Java SPI實際上就是基於介面的編程+策略模式+配置文件組合實現的動態載入機制。
它為某個介面尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程式之外,在模式化設計中這個機制尤其重要,所以它的核心思想是解耦

使用場景

  • 資料庫驅動載入介面實現類的載入
    JDBC載入不同類型資料庫的驅動
  • 日誌門面介面實現類載入
    SLF4J載入不同提供商的日誌實現類
  • Spring
  • Dubbo

使用說明

  1. 當服務提供者提供了介面的一種具體實現後,在jar包的META-INF/service目錄下創建一個以"介面全限定名"為命名的文件,內容為實現類的全限定名。
  2. 介面實現類所在的jar包放在主程式的classpath中
  3. 主程式通過java.util.ServiceLoader動態載入實現模板,它通過掃描META-INF/services目錄下的配置文件找到實現類的全限定名,把類載入到JVM
  4. SPI的實現類必須攜帶一個不帶參數的構造方法
public final class ServiceLoader<S> implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // 代表被載入的類或者介面
    private final Class<S> service;
    // 用於定位、載入和實例化providers的類載入器
    private final ClassLoader loader;
    // 創建ServiceLoader時採用的訪問控制上下文
    private final AccessControlContext acc;
    // 緩存providers,按照實例化的順序排序
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 懶查找迭代器
    private LazyIterator lookupIterator;

    //重新載入,就相當於重新創建ServiceLoader了,用於新的服務提供者安裝到正在運行的Java虛擬機
    public void reload() {
        //清空緩存中所有已實例化的服務提供者
        providers.clear();
        //新建一個迭代器,該迭代器會從頭查找和實例化服務提供者。
        lookupIterator = new LazyIterator(service, loader);
    }

    /**
    ** 私有構造器
    ** 使用指定的類載入器和服務創建服務載入器
    ** 如果沒有指定類載入器,使用系統類載入器,就是應用類載入器
    **/
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

   //解析失敗處理的方法
    private static void fail(Class<?> service, String msg, Throwable cause)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                            cause);
    }

    private static void fail(Class<?> service, String msg)
        throws ServiceConfigurationError
    {
        throw new ServiceConfigurationError(service.getName() + ": " + msg);
    }

    private static void fail(Class<?> service, URL u, int line, String msg)
        throws ServiceConfigurationError
    {
        fail(service, u + ":" + line + ": " + msg);
    }

    //解析服務提供者配置文件中的一行
    //首先去掉註釋檢驗,然後保存
    //返回下一行行號
    //重覆的配置項不會被保存
    private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
                          List<String> names)
        throws IOException, ServiceConfigurationError
    {
        String ln = r.readLine();
        if (ln == null) {
            return -1;
        }
        int ci = ln.indexOf('#');
        if (ci >= 0) ln = ln.substring(0, ci);
        ln = ln.trim();
        int n = ln.length();
        if (n != 0) {
            if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                fail(service, u, lc, "Illegal configuration-file syntax");
            int cp = ln.codePointAt(0);
            if (!Character.isJavaIdentifierStart(cp))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
            for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                cp = ln.codePointAt(i);
                if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
            }
            if (!providers.containsKey(ln) && !names.contains(ln))
                names.add(ln);
        }
        return lc + 1;
    }

  //解析配置文件,解析指定的url配置文件
  //使用parseLine方法進行解析,未被實例化的服務提供者會被保存到緩存中。
    private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        return names.iterator();
    }

    //服務提供者查找的迭代器
    private class LazyIterator implements Iterator<S>
    {
        //服務提供者介面
        Class<S> service;
        //類載入器
        ClassLoader loader;
        //保存實現類的url
        Enumeration<URL> configs = null;
        //保存實現類的全名
        Iterator<String> pending = null;
        //迭代器中下一個實現類的全名
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }
    //獲取迭代器
    //返回遍歷服務提供者的迭代器
    //以懶載入的方式載入可用的服務提供者
    //懶載入的實現是:解析配置文件和實例化服務提供者的工作由迭代器本身完成
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

   //為指定的服務使用指定的類載入器來創建一個ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

   //使用線程上下文的類載入器來創建一個ServiceLoader
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

   //使用擴展類載入器為指定的服務創建ServiceLoader
   //只能找到並載入已經安裝到當前Java虛擬機中的服務提供者,應用程式類路徑中的服務提供者將被忽略
    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    /**
     * Returns a string describing this service.
     *
     * @return  A descriptive string
     */
    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }

}

ServiceLoader不是實例化以後,就去讀取文件的具體實現。而是等到使用迭代器去遍歷的時候,才會載入對應的配置文件去解析,調用hasNext方法時就去載入配置文件進行解析,調用Next方法的時候進行實例化並緩存。

優點
使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程式可以根據實際業務情況啟用框架擴展或替代框架組件。

缺點
雖然ServiceLoader也算是使用的延遲載入,但是基本只能通過遍歷全部獲取,也就是介面的實現類全部載入並實例化一遍。如果你並不想用某些實現類,它也被載入並實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。
多個併發多線程使用ServiceLoader類的實例是不安全的。

Dubbo的SPI機制

3.png

從圖中可以看出,Dubbo進行各個模塊的擴展時,是通過ExtensionLoader與擴展點進行關聯的。
在Dubbo中的擴展點需要滿足以下幾個特點:

  1. 擴展點必須是Interface類型,必須被@SPI註釋
  2. 配置文件存儲在META-INF/services/META-INF/dubbo/META-INF/dubbo/internal,這些路徑下定義的文件名為擴展點介面的全類名,文件中以鍵值對的形式配置擴展點的擴展實現,這與JDk SPI的存儲形式有很大不同,所以在Dubbo中無法直接使用ServiceLoader, 而是使用ExtensionLoader,可用於載入Dubbo中的各種可配置組件,比如動態代理方式(ProxyFactory)、負載均衡策略(LoadBalance)、RCP協議(Protocol)、攔截器(Filter)、容器類型(Container)、集群方式(Cluster)和註冊中心類型等。
    META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory 中定義的擴展 :
adaptive = com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory 
spi = com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory 
spring = com.alibaba.dubbo.config.spring.extension.SpringExtensionFactor

在標識擴展點時會用到這幾個標識,@SPI 、 @Adaptive、 @Activate

@SPI (註解在類上):該註解標識了介面是一個擴展點,屬性value用來指定預設適配擴展點的名稱。
@Activate(註解在類型和方法上):@Activate註解在擴展點的實現類上,表示了一個擴展類被獲取到的條件,符合條件就被獲取,不符合條件就不獲取,根據@Activate中的group、value屬性來過濾。
@Adaptive(註解在類型和方法上):如果註解在類上,這個類就是預設的適配擴展。註解在擴展點Interface的方法上時,dubbo會動態的生成一個這個擴展點的適配擴展類(生成代碼,動態編譯實例化Class),名稱為擴展點Interface的簡單類名+$Adaptive,這樣做的目的是為了在運行時去適配不同的擴展實例,在運行時通過傳入的URL類型的參數或者內部含有獲取URL方法的參數,從URL中獲取到要使用的擴展類的名稱,再去根據名稱載入對應的擴展實例,用這個擴展實例對象調用相同的方法。如果運行時沒有適配到運行的擴展實例,那麼就使用@SPI註解預設指定的擴展。通過這種方式就實現了運行時去適配到對應的擴展。
我們隨機找一個源碼中定義的介面: Transporter

@SPI("netty")
public interface Transporter {
    // 綁定一個伺服器
    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    // 連接一個伺服器,即創建一個客戶端
    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;
}

ExtensionLoader會通過createAdaptiveExtensionClassCode方法動態生成一個Transporter$Adaptive類,生成的代碼如下:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
    
public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        //URL參數為空則拋出異常。
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        
        return extension.bind(arg0, arg1);
    }
}

這些代碼都是模板代碼,最核心的代碼只有一行,是為了去獲取指定名稱的擴展實例對象。
com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);

擴展載入器 ExtensionLoader

它控制著所有擴展點的初始化、載入擴展的過程。
ExtensionLoader中會存儲兩個靜態屬性,EXTENSION_LOADERS保存內核開放的擴展點對應的ExtensionLoader實例對象;EXTENSION_INSTANCES保存了擴展類型(Class)和擴展類型的實例對象

private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);

    //這是jdk的SPI擴展機制中配置文件路徑,dubbo為了相容jdk的SPI
    private static final String SERVICES_DIRECTORY = "META-INF/services/";

    //用於用戶自定義的擴展實現配置文件存放路徑
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";

    //用於dubbo內部提供的擴展實現配置文件存放路徑
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");

    //擴展載入器集合,key為擴展介面,例如Protocol等
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

    //擴展實現集合,key為擴展實現類,value為擴展對象
    //例如key為Class<DubboProtocol>,value為DubboProtocol對象
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    //擴展介面,例如Protocol等
    private final Class<?> type;

    //對象工廠,獲得擴展實現的實例,用於injectExtension方法中將擴展實現類的實例註入到相關的依賴屬性。
    //比如StubProxyFactoryWrapper類中有Protocol protocol屬性,就是通過set方法把Protocol的實現類實例賦值
    private final ExtensionFactory objectFactory;

    //以下提到的擴展名就是在配置文件中的key值,類似於“dubbo”等

    //緩存的擴展名與擴展類映射,和cachedClasses的key和value對換。
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();

    //緩存的擴展實現類集合
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

    //擴展名與加有@Activate的自動激活類的映射
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

    //緩存的擴展對象集合,key為擴展名,value為擴展對象
    //例如Protocol擴展,key為dubbo,value為DubboProcotol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

    //緩存的自適應( Adaptive )擴展對象,例如例如AdaptiveExtensionFactory類的對象
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

    //緩存的自適應擴展對象的類,例如AdaptiveExtensionFactory類
    private volatile Class<?> cachedAdaptiveClass = null;

    //緩存的預設擴展名,就是@SPI中設置的值
    private String cachedDefaultName;

    //創建cachedAdaptiveInstance異常
    private volatile Throwable createAdaptiveInstanceError;

    //拓展Wrapper實現類集合
    private Set<Class<?>> cachedWrapperClasses;

    //拓展名與載入對應拓展類發生的異常的映射
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

ExtensionLoader沒有提供public的構造方法,有一個私有的構造方法,獲取ExtensionLoader實例的工廠方法,但是提供了一個public static的getExtensionLoader。其public成員方法中有三個比較重要的方法:
getActiveExtension: 根據條件獲取當前擴展可自動激活的實現
getExtension: 根據名稱獲取當前擴展的指定實現
getAdaptiveExtension: 獲取當前擴展的自適應實現

 private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);

}

從上可以看出ExtensionFactory也是一個擴展點,有兩個實現類:SpiExtensionFactoryAdaptiveExtensionFactory,實際上還有一個SpringExtensionFactory,不同的實現類可以用不同的方式來完成擴展點實現的載入。如果要載入的擴展點類型是ExtensionFactory,那麼object設置為null。
預設的ExtensionFactory實現中,AdaptiveExtensionFactory被@Adaptive註解註釋,也就是說它是ExtensionFactory對應的自適應擴展實現(每個擴展點最多只能有一個自適應實現,如果所有實現中沒有被@Adaptive註釋的,那麼dubbo會動態生成一個自適應實現類)

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //擴展對象的集合,預設的可以分為dubbo 的SPI中介面實現類對象或者Spring bean對象
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //遍歷所有支持的擴展名
        for (String name : loader.getSupportedExtensions()) {
            //擴展對象加入到集合中
            list.add(loader.getExtension(name));
        }
        //返回一個不可修改的集合
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            //通過擴展介面和擴展名獲得擴展對象
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

上述代碼中調用到了ExtensionLoader類中的getSupportedExtensions方法,所以接下來再分析ExtensionLoader類。

 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //擴展點介面為空,拋出異常
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //判斷type是否是一個介面類
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //判斷是否為可擴展的介面
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        //從擴展載入器集合中取出擴展介面對應的擴展載入器
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);

        //如果為空,則創建該擴展介面的擴展載入器,並且添加到EXTENSION_LOADERS
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //創建適配器對象
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

在ExtensionLoader的私有構造方法中可以看出,在選擇ExtensionFactory的時候,並不是用getExtension(name)來獲取某個具體的實現類,而是調用getAdaptiveExtension來獲取一個自適應的實現。
首先檢查緩存的adaptiveInstance是否存在,如果存在則直接使用,否則的話調用createAdaptiveExtension方法來創建新的adaptiveInstance並且緩存起來,也就是說對於某個擴展點,每次調用ExtensionLoader.getAdaptiveExtension獲取到的都是同一個實例。
在調用getAdaptiveExtensionClass中首先調用getExtensionClasses()
在getAdaptiveExtensionClass()中,調用getExtensionClasses()獲取擴展實現類數組,並存放在cachedClasses屬性中。
再從getExtensionClasses()看,當cachedClasses為空時,調用loadExtensionClasses()
getExtensionClasses()會載入當前Extension的所有實現,如果有@Adaptive類型,則會賦值給cachedAdaptiveClass屬性緩存起來,如果沒有找到@Adaptive類型實現,則動態創建一個AdaptiveExtensionClass。

首先會獲取到該擴展點類的註解中的值,獲取預設值,然後從特定目錄下讀取配置文件中的信息,
最後通過loadClass,將有關類放到extensionClasses變數中

 private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
 private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        //緩存的自適應擴展對象
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }


private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            //@SPI內的預設值
            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;
            //獲取ExtensionLoader類信息
            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);
        }
    }

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                String line;
                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) {
                                //根據"="拆分key跟value
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //載入擴展類
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //該類是否實現擴展介面
        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 if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            //通過反射獲得構造器對象
            clazz.getConstructor();
            //未配置擴展名,自動生成,例如DemoFilter為 demo,主要用於相容java SPI的配置。
            if (name == null || name.length() == 0) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 獲得擴展名,可以是數組,有多個拓擴展名。
            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());
                    }
                }
            }
        }
    }

上述代碼完成了自適應擴展點類型的實現和實例化,下麵方法是擴展點自動註入的實現,它會獲取處理當前實例的所有set方法對應的參數類型和property名稱,根據這兩個條件從ExtensionFactory中查詢,如果有返回擴展點實例,那麼就進行註入操作。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                //反射獲得該類中所有的方法
                for (Method method : instance.getClass().getMethods()) {
                    //如果是set方法
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            //獲得屬性,比如StubProxyFactoryWrapper類中有Protocol protocol屬性,
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //獲得屬性值,比如Protocol對象,也可能是Bean對象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                //註入依賴屬性
                                method.invoke(instance, object);
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

文章參考:
dubbo源碼一:ExtensionLoader及獲取適配類過程解析
Dubbo擴展點載入機制 - ExtensionLoader
【Dubbo】Adaptive


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在本版本中引入了SPI機制,關於Java的SPI機制與Dubbo的SPI機制在以前的文章中介紹過。 傳送門: "Dubbo的SPI機制與JDK機制的不同及原理分析" 因為設計的RPC框架是基於Spring的,時常會遇到依賴註入問題。Spring中也有SPI機制,但是它有有個缺點,就是在利用SPI機制 ...
  • 在前兩個版本中,每次發起請求一次就新建一個netty的channel連接,如果在高併發情況下就會造成資源的浪費,這時實現 非同步請求 就十分重要,當有多個請求線程時,需要設計一個 線程池 來進行管理。除此之外,當前方法過於依賴註冊中心,在高併發情況下對註冊中心造成了壓力;另外如果註冊中心出現宕機等情況 ...
  • 在上一個版本中利用netty實現了簡單的一對一的RPC,需要手動設置服務地址,限制性較大。 在本文中,利用zookeeper作為服務註冊中心,在服務端啟動時將本地的服務信息註冊到zookeeper中,當客戶端發起遠程服務調用時,先從zookeeper中獲取該服務的地址,然後根據獲得的這個地址來利用n ...
  • 什麼是RPC RPC (Remote Procedure Call Protocol), 遠程過程調用,通俗的解釋就是:客戶端在不知道調用細節的情況下,調用存在於遠程電腦上的某個對象,就像調用本地應用程式中的對象一樣,不需要瞭解底層網路技術的協議。 簡單的整體工作流程 請求端發送一個調用的數據包, ...
  • 在dubbo中,關於註冊中心Registry的有關實現封裝在了dubbo registry模塊中。提供者(Provider)個消費者(Consumer)都是通過註冊中心進行資源的調度。當服務啟動時,provider會調用註冊中心的register方法將自己的服務通過url的方式發佈到註冊中心,而co ...
  • 在網路傳輸中,怎麼確保通道連接的可用性是一個很重要的問題,簡單的說,在網路通信中有客戶端和服務端,一個負責發送請求,一個負責接收請求,在保證連接有效性的背景下,這兩個物體扮演了什麼角色,心跳機制能有效的保證連接的可用性,那它的機制是什麼,下文中將會詳細講解。 網路層的可用性 首先講一下TCP,在du ...
  • 在上文中介紹了基礎類AbstractRegistry類的解釋,在本篇中將繼續介紹該包下的其他類。 FailbackRegistry 該類繼承了AbstractRegistry,AbstractRegistry中的註冊訂閱等方法,實際上就是一些記憶體緩存的變化,而真正的註冊訂閱的實現邏輯在Failbac ...
  • [TOC] python是數據分析的主要工具,它包含的數據結構和數據處理工具的設計讓python在數據分析領域變得十分快捷。它以NumPy為基礎,並對於需要類似 for迴圈 的大量數據處理的問題有非常快捷的數組處理函數。 但是pandas最擅長的領域還是在處理表格型二維以上不同數據類型數據。 基本導 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...