亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

dubbo之SPI

UnixAgain / 2107人閱讀

摘要:簡介全稱為,是一種服務發(fā)現(xiàn)機制。的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。不過,并未使用原生的機制,而是對其進行了增強,使其能夠更好的滿足需求。并未使用,而是重新實現(xiàn)了一套功能更強的機制。

1、SPI簡介

SPI 全稱為 Service Provider Interface,是一種服務發(fā)現(xiàn)機制。SPI 的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。這樣可以在運行時,動態(tài)為接口替換實現(xiàn)類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。SPI 機制在第三方框架中也有所應用,比如 Dubbo 就是通過 SPI 機制加載所有的組件。不過,Dubbo 并未使用 Java 原生的 SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重要的模塊?;?SPI,我們可以很容易的對 Dubbo 進行拓展。如果大家想要學習 Dubbo 的源碼,SPI 機制務必弄懂。接下來,我們先來了解一下 Java SPI 與 Dubbo SPI 的用法,然后再來分析 Dubbo SPI 的源碼。

2、SPI示例 2.1 Java SPI示例

本節(jié)通過一個示例演示 Java SPI 的使用方法。首先,我們定義一個接口,名稱為 HelloService。

public interface HelloService {
    void sayHello();
}

家下來定義兩個實現(xiàn)類:HelloAService、HelloBService;

public class HelloAService implements HelloService {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am A");
    }
}

public class HelloBService implements HelloService {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am B");
    }
}

接下來 META-INF/services 文件夾下創(chuàng)建一個文件,名稱為 Robot 的全限定名 org.apache.spi.Robot。文件內(nèi)容為實現(xiàn)類的全限定的類名,如下:

org.apache.spi.HelloAService
org.apache.spi.HelloBService

做好所需的準備工作,接下來編寫代碼進行測試

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader serviceLoader = ServiceLoader.load(HelloService.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(HelloService::sayHello);
    }
}

最后結(jié)果如下:

Java SPI
Hello, I am A
Hello, I am B

從測試結(jié)果可以看出,我們的兩個實現(xiàn)類被成功的加載,并輸出了相應的內(nèi)容。關于 Java SPI 的演示先到這里,接下來演示 Dubbo SPI。

2.2 Dubbo SPI

Dubbo 并未使用 Java SPI,而是重新實現(xiàn)了一套功能更強的 SPI 機制。Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現(xiàn)類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下,配置內(nèi)容如下。

helloAService = org.apache.spi.HelloAService
helloBService = org.apache.spi.HelloBService

與 Java SPI 實現(xiàn)類配置不同,Dubbo SPI 是通過鍵值對的方式進行配置,這樣我們可以按需加載指定的實現(xiàn)類。另外,在測試 Dubbo SPI 時,需要在 Robot 接口上標注 @SPI 注解。下面來演示 Dubbo SPI 的用法:

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader extensionLoader = 
            ExtensionLoader.getExtensionLoader(HelloService.class);
        HelloService a = extensionLoader.getExtension("HelloAService");
        a.sayHello();
        HelloService b = extensionLoader.getExtension("HelloBService");
        b.sayHello();
    }
}

測試結(jié)果如下:

Java SPI
Hello, I am A
Hello, I am B
3、Dubbo SPI源碼解析

Dubbo SPI相關邏輯都在ExtensionLoader 類中,首先通過getExtensionLoader獲取一個ExtensionLoader實例,然后在根據(jù)getExtension獲取type的擴展類。
getExtensionLoader方法比較簡單,先從緩存中獲取,如果緩存不存在,則創(chuàng)建ExtensionLoader對象,并存入緩存中,在看getExtension方法:

    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 根據(jù)擴展名從緩存中獲取,緩存中沒有,創(chuàng)建并存入緩存
        Holder holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        // 雙重檢查
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 根據(jù)擴展名創(chuàng)建實例對象
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

上面代碼的邏輯比較簡單,首先檢查緩存,緩存未命中則創(chuàng)建拓展對象。下面我們來看一下創(chuàng)建拓展對象的過程是怎樣的。

    private T createExtension(String name) {
        // 從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關系表
        Class clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            Set> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class wrapperClass : wrapperClasses) {
                    // 將當前 instance 作為參數(shù)傳給 Wrapper 的構(gòu)造方法,并通過反射創(chuàng)建 Wrapper 實例。
                    // 然后向 Wrapper 實例中注入依賴,最后將 Wrapper 實例再次賦值給 instance 變量
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

我們在通過名稱獲取拓展類之前,首先需要根據(jù)配置文件解析出拓展項名稱到拓展類的映射關系表(Map<名稱, 拓展類>),之后再根據(jù)拓展項名稱從映射關系表中取出相應的拓展類即可。相關過程的代碼分析如下

    private Map> getExtensionClasses() {
        // 從緩存中獲取
        Map> classes = cachedClasses.get();
        // 雙重檢查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加載所有的擴展類
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

getExtensionClasses方法同樣是先從緩存中讀取,緩存不存在,在去加載:

    private Map> loadExtensionClasses() {
        // 獲取擴展類SPI注解
        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> extensionClasses = new HashMap>();
        // 加載指定文件夾下的配置文件
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

loadExtensionClasses 方法總共做了兩件事情,一是對 SPI 注解進行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。SPI 注解解析過程比較簡單,無需多說。下面我們來看一下 loadDirectory 做了哪些事情。

    private void loadDirectory(Map> extensionClasses, String dir, String type) {
        // fileName = 文件夾路徑 + type 全限定名 
        String fileName = dir + type;
        try {
            Enumeration urls;
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                // 根據(jù)文件名加載所有的同名文件
                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);
        }
    }

loadDirectory 方法先通過 classLoader 獲取所有資源鏈接,然后再通過 loadResource 方法加載資源。我們繼續(xù)跟下去,看一下 loadResource 方法的實現(xiàn)。

    private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                String line;
                // 讀取文件中內(nèi)容
                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) {
                                // 加載類,并通過 loadClass 方法對類進行緩存
                                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);
        }
    }

loadClass()方法主要是利用反射原理,根據(jù)類的權(quán)限定名加載成類,并存入緩存中

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/73240.html

相關文章

  • dubboSPI自適應擴展機制

    摘要:對于這個矛盾的問題,通過自適應拓展機制很好的解決了。自適應拓展機制的實現(xiàn)邏輯比較復雜,首先會為拓展接口生成具有代理功能的代碼。 1、背景 在 Dubbo 中,很多拓展都是通過 SPI 機制進行加載的,比如 Protocol、Cluster、LoadBalance 等。有時,有些拓展并不想在框架啟動階段被加載,而是希望在拓展方法被調(diào)用時,根據(jù)運行時參數(shù)進行加載。這聽起來有些矛盾。拓展未被...

    vvpale 評論0 收藏0
  • 聊聊Dubbo - Dubbo可擴展機制源碼解析

    摘要:什么是類那什么樣類的才是擴展機制中的類呢類是一個有復制構(gòu)造函數(shù)的類,也是典型的裝飾者模式。代碼如下有一個參數(shù)是的復制構(gòu)造函數(shù)有一個構(gòu)造函數(shù),參數(shù)是擴展點,所以它是一個擴展機制中的類。 摘要:?在Dubbo可擴展機制實戰(zhàn)中,我們了解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現(xiàn),并自己實現(xiàn)了一個LoadBalance。是不是覺得Dubbo的擴展機制很不錯呀...

    lmxdawn 評論0 收藏0
  • 聊聊Dubbo - Dubbo可擴展機制實戰(zhàn)

    摘要:今天我想聊聊的另一個很棒的特性就是它的可擴展性。的擴展機制在的官網(wǎng)上,描述自己是一個高性能的框架。接下來的章節(jié)中我們會慢慢揭開擴展機制的神秘面紗。擴展擴展點的實現(xiàn)類。的定義在配置文件中可以看到文件中定義了個的擴展實現(xiàn)。 摘要: 在Dubbo的官網(wǎng)上,Dubbo描述自己是一個高性能的RPC框架。今天我想聊聊Dubbo的另一個很棒的特性, 就是它的可擴展性。 Dubbo的擴展機制 在Dub...

    techstay 評論0 收藏0
  • dubbo源碼解析(二)Dubbo擴展機制SPI

    摘要:二注解該注解為了保證在內(nèi)部調(diào)用具體實現(xiàn)的時候不是硬編碼來指定引用哪個實現(xiàn),也就是為了適配一個接口的多種實現(xiàn),這樣做符合模塊接口設計的可插拔原則,也增加了整個框架的靈活性,該注解也實現(xiàn)了擴展點自動裝配的特性。 Dubbo擴展機制SPI 前一篇文章《dubbo源碼解析(一)Hello,Dubbo》是對dubbo整個項目大體的介紹,而從這篇文章開始,我將會從源碼來解讀dubbo再各個模塊的實...

    DirtyMind 評論0 收藏0
  • Dubbo Spi機制

    摘要:為了實現(xiàn)在模塊裝配的時候,不在模塊里寫死代碼,就需要一種服務發(fā)現(xiàn)機制。就提供了這樣一種機制為某個接口尋找服務實現(xiàn),有點類似思想,將裝配的控制權(quán)移到代碼之外。即接口文件的全類名。五示例遵循上述第一條第點,這里為接口文件,其中和為兩個實現(xiàn)類。 一、Dubbo內(nèi)核 Dubbo內(nèi)核主要包含SPI、AOP、IOC、Compiler。 二、JDK的SPI 1.spi的設計目標: 面向?qū)ο蟮脑O計里...

    mrli2016 評論0 收藏0

發(fā)表評論

0條評論

UnixAgain

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<