Java SPI服務(wù)發(fā)現(xiàn)及Dubbo和Spring中SPI的應(yīng)用

一裸删、SPI簡(jiǎn)介

SPI 全稱為 (Service Provider Interface) 兼呵,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制满败。 目前有不少框架用它來(lái)做服務(wù)的擴(kuò)展發(fā)現(xiàn), 簡(jiǎn)單來(lái)說(shuō)粪摘,它就是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制瀑晒, 舉個(gè)例子來(lái)說(shuō), 有個(gè)接口赶熟,想運(yùn)行時(shí)動(dòng)態(tài)的給它添加實(shí)現(xiàn)瑰妄,你只需要添加一個(gè)實(shí)現(xiàn)陷嘴,而后映砖,把新加的實(shí)現(xiàn),描述給JDK知道就行啦(通過(guò)改一個(gè)文本文件即可)我們經(jīng)常遇到的就是java.sql.Driver接口灾挨,其他不同廠商可以針對(duì)同一接口做出不同的實(shí)現(xiàn)邑退,mysql和postgresql都有不同的實(shí)現(xiàn)提供給用戶,而Java的SPI機(jī)制可以為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)

SPI 機(jī)制的思想來(lái)源正是:開閉原則劳澄,對(duì)擴(kuò)展開放地技,對(duì)修改關(guān)閉。對(duì)于一個(gè)特定的程序秒拔,如 Java 的 java.sql.Driver莫矗,就是一個(gè)接口,Mysql 的 Driver 實(shí)現(xiàn)就是通過(guò) SPI 機(jī)制被程序加載的砂缩。不難想到作谚,其他 Driver 的實(shí)現(xiàn)也可以依靠 SPI 機(jī)制去實(shí)現(xiàn)。

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1gg8d7uxz52j30gq037dfp.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />

類圖中庵芭,接口對(duì)應(yīng)定義的抽象SPI接口妹懒;實(shí)現(xiàn)方實(shí)現(xiàn)SPI接口;調(diào)用方依賴SPI接口双吆。

SPI接口的定義在調(diào)用方眨唬,在概念上更依賴調(diào)用方会前;組織上位于調(diào)用方所在的包中;實(shí)現(xiàn)位于獨(dú)立的包中匾竿。

當(dāng)接口屬于實(shí)現(xiàn)方的情況瓦宜,實(shí)現(xiàn)方提供了接口和實(shí)現(xiàn),這個(gè)用法很常見岭妖,屬于API調(diào)用歉提。我們可以引用接口來(lái)達(dá)到調(diào)用某實(shí)現(xiàn)類的功能。

二区转、Java SPI

當(dāng)服務(wù)的提供者提供了一種接口的實(shí)現(xiàn)之后苔巨,需要在classpath下的META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,這個(gè)文件里的內(nèi)容就是這個(gè)接口的具體的實(shí)現(xiàn)類废离。當(dāng)其他的程序需要這個(gè)服務(wù)的時(shí)候侄泽,就可以通過(guò)查找這個(gè)jar包(一般都是以jar包做依賴)的META-INF/services/中的配置文件,配置文件中有接口的具體實(shí)現(xiàn)類名蜻韭,可以根據(jù)這個(gè)類名進(jìn)行加載實(shí)例化悼尾,就可以使用該服務(wù)了。JDK中查找服務(wù)實(shí)現(xiàn)的工具類是:java.util.ServiceLoader肖方。

2.1 SPI接口
package tim.wang.sourcecode.spi;

/**
 * 定義一個(gè)接口和多個(gè)實(shí)現(xiàn)
 * @author wangjun
 * @date 2020-06-28
 */
public interface Robot {
    void sayHello();
}

定義了一個(gè)機(jī)器人接口闺魏,有一個(gè)sayHello方法

2.2 SPI具體實(shí)現(xiàn)
package tim.wang.sourcecode.spi;

/**
 * 擎天柱實(shí)現(xiàn)
 * @author wangjun
 * @date 2020-06-28
 */
public class OptimusPrime implements Robot{
    @Override
    public void sayHello() {
        System.out.println("hello, I am Optimus Prime.");
    }
}

package tim.wang.sourcecode.spi;

/**
 * 大黃蜂 實(shí)現(xiàn)
 * @author wangjun
 * @date 2020-06-28
 */

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

2.3 增加META-INF目錄文件

Resource下面創(chuàng)建META-INF/services 目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1gg8dsr8ufmj30g603idfp.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />

tim.wang.sourcecode.spi.Bumblebee
tim.wang.sourcecode.spi.OptimusPrime
2.4 增加RobotService
package tim.wang.sourcecode.spi;

import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;

/**
 * @author wangjun
 * @date 2020-06-28
 */
public class RobotService {
    public Robot getRobot() {
        ServiceLoader<Robot> serializers = ServiceLoader.load(Robot.class);
        final Optional<Robot> serializer = StreamSupport.stream(serializers.spliterator(), false)
                .findFirst();
        return serializer.orElse(new OptimusPrime());
    }

    public static void main(String[] args) {
        RobotService robotService = new RobotService();
        Robot robot = robotService.getRobot();
        robot.sayHello();
    }
}
2.5 SPI的用途

數(shù)據(jù)庫(kù)DriverManager、Spring俯画、ConfigurableBeanFactory等都用到了SPI機(jī)制析桥,這里以數(shù)據(jù)庫(kù)DriverManager為例,看一下其實(shí)現(xiàn)的內(nèi)幕艰垂。

DriverManager是jdbc里管理和注冊(cè)不同數(shù)據(jù)庫(kù)driver的工具類泡仗。針對(duì)一個(gè)數(shù)據(jù)庫(kù),可能會(huì)存在著不同的數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn)猜憎。我們?cè)谑褂锰囟ǖ尿?qū)動(dòng)實(shí)現(xiàn)時(shí)娩怎,不希望修改現(xiàn)有的代碼,而希望通過(guò)一個(gè)簡(jiǎn)單的配置就可以達(dá)到效果胰柑。 在使用mysql驅(qū)動(dòng)的時(shí)候截亦,會(huì)有一個(gè)疑問(wèn),DriverManager是怎么獲得某確定驅(qū)動(dòng)類的柬讨?我們?cè)谶\(yùn)用Class.forName("com.mysql.jdbc.Driver")加載mysql驅(qū)動(dòng)后崩瓤,就會(huì)執(zhí)行其中的靜態(tài)代碼把driver注冊(cè)到DriverManager中,以便后續(xù)的使用姐浮。

在JDBC4.0之前谷遂,連接數(shù)據(jù)庫(kù)的時(shí)候,通常會(huì)用Class.forName("com.mysql.jdbc.Driver")這句先加載數(shù)據(jù)庫(kù)相關(guān)的驅(qū)動(dòng)卖鲤,然后再進(jìn)行獲取連接等的操作肾扰。而JDBC4.0之后不需要Class.forName來(lái)加載驅(qū)動(dòng)畴嘶,直接獲取連接即可,這里使用了Java的SPI擴(kuò)展機(jī)制來(lái)實(shí)現(xiàn)集晚。

在java中定義了接口java.sql.Driver窗悯,并沒有具體的實(shí)現(xiàn),具體的實(shí)現(xiàn)都是由不同廠商來(lái)提供的偷拔。

2.6 Mysql DriverManager實(shí)現(xiàn)

我們?cè)趺慈ゴ_定使用哪個(gè)數(shù)據(jù)庫(kù)連接的驅(qū)動(dòng)呢蒋院?這里就涉及到使用Java的SPI擴(kuò)展機(jī)制來(lái)查找相關(guān)驅(qū)動(dòng)的東西了,關(guān)于驅(qū)動(dòng)的查找其實(shí)都在DriverManager中莲绰,DriverManager是Java中的實(shí)現(xiàn)欺旧,用來(lái)獲取數(shù)據(jù)庫(kù)連接,在DriverManager中有一個(gè)靜態(tài)代碼塊如下:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

可以看到其內(nèi)部的靜態(tài)代碼塊中有一個(gè)loadInitialDrivers方法蛤签,loadInitialDrivers用法用到了上文提到的spi工具類ServiceLoader:

    public Void run() {

        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();

        /* Load these drivers, so that they can be instantiated.
         * It may be the case that the driver class may not be there
         * i.e. there may be a packaged driver with the service class
         * as implementation of java.sql.Driver but the actual class
         * may be missing. In that case a java.util.ServiceConfigurationError
         * will be thrown at runtime by the VM trying to locate
         * and load the service.
         *
         * Adding a try catch block to catch those runtime errors
         * if driver not available in classpath but it's
         * packaged as service and that service is there in classpath.
         */
        try{
            while(driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch(Throwable t) {
        // Do nothing
        }
        return null;
    }

遍歷使用SPI獲取到的具體實(shí)現(xiàn)辞友,實(shí)例化各個(gè)實(shí)現(xiàn)類。在遍歷的時(shí)候震肮,首先調(diào)用driversIterator.hasNext()方法称龙,這里會(huì)搜索classpath下以及jar包中所有的META-INF/services目錄下的java.sql.Driver文件,并找到文件中的實(shí)現(xiàn)類的名字戳晌,此時(shí)并沒有實(shí)例化具體的實(shí)現(xiàn)類鲫尊。

2.7 SPI解決的問(wèn)題場(chǎng)景描述

在我們?cè)O(shè)計(jì)一套API供別人調(diào)用的時(shí)候,如果同一個(gè)功能的要求特別多沦偎,或者同一個(gè)接口要面對(duì)很復(fù)雜的業(yè)務(wù)場(chǎng)景疫向,這個(gè)時(shí)候我們?cè)撛趺崔k呢?

  • 其一:我們可以規(guī)范不同的系統(tǒng)調(diào)用扛施,也就是傳遞一個(gè)系統(tǒng)標(biāo)識(shí)鸿捧;
  • 其二:我們?cè)趦?nèi)部編碼的時(shí)候可以使用不同的條件判斷語(yǔ)句進(jìn)行處理;
  • 其三:我們可以寫幾個(gè)策略類來(lái)來(lái)應(yīng)對(duì)這個(gè)復(fù)雜的業(yè)務(wù)邏輯疙渣,比如同一個(gè)功能的實(shí)現(xiàn),A實(shí)現(xiàn)類與B實(shí)現(xiàn)類的邏輯一點(diǎn)也不一樣堆巧,但是目標(biāo)是一樣的妄荔,這個(gè)時(shí)候使用策略類是毋庸置疑的?

三谍肤、Spring SPI

Spring中使用的類是SpringFactoriesLoader啦租,在org.springframework.core.io.support包中

  1. SpringFactoriesLoader 會(huì)掃描 classpath 中的 META-INF/spring.factories文件。
  2. SpringFactoriesLoader 會(huì)加載并實(shí)例化 META-INF/spring.factories 中的制定類型
  3. META-INF/spring.factories 內(nèi)容必須是 properties 的Key-Value形式荒揣,多值以逗號(hào)隔開篷角。
3.1 使用

和 SPI 不同,由于 SpringFactoriesLoader 中的配置文件格式是 properties 文件系任,因此恳蹲,不需要要像 SPI 中那樣為每個(gè)服務(wù)都創(chuàng)建一個(gè)文件虐块, 而是選擇直接把所有服務(wù)都扔到 META-INF/spring.factories 文件中。

com.fsx.serviceloader.IService=com.fsx.serviceloader.HDFSService,com.fsx.serviceloader.LocalService

// 若有非常多個(gè)需要換行 可以這么寫
// 前面是否頂頭沒關(guān)系(Spring在4.x版本修復(fù)了這個(gè)bug)
com.fsx.serviceloader.IService=\
    com.fsx.serviceloader.HDFSService,\
    com.fsx.serviceloader.LocalService
 public static void main(String[] args) throws IOException {
        List<IService> services = SpringFactoriesLoader.loadFactories(IService.class, Main.class.getClassLoader());
        List<String> list = SpringFactoriesLoader.loadFactoryNames(IService.class, Main.class.getClassLoader());
        System.out.println(list); //[com.fsx.serviceloader.HDFSService, com.fsx.serviceloader.LocalService]
        System.out.println(services); //[com.fsx.serviceloader.HDFSService@794cb805, com.fsx.serviceloader.LocalService@4b5a5ed1]
    }

使用細(xì)節(jié):

  • spring.factories內(nèi)容的key不只能是接口嘉蕾,也可以是抽象類贺奠、具體的類。但是有個(gè)原則:=后面必須是key的實(shí)現(xiàn)類(子類)
  • key還可以是注解错忱,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration儡率,它就是一個(gè)注解
  • 文件的格式需要保證正確,否則會(huì)返回[](不會(huì)報(bào)錯(cuò))
  • =右邊必須不是抽象類以清,必須能夠?qū)嵗铡G矣锌盏臉?gòu)造函數(shù)~
  • loadFactories依賴方法loadFactoryNamesloadFactoryNames方法只拿全類名掷倔,loadFactories拿到全類名后會(huì)立馬實(shí)例化
  • 此處特別注意loadFactories實(shí)例化完成所有實(shí)例后箕肃,會(huì)調(diào)用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的今魔,這個(gè)特點(diǎn)特別的重要勺像。
3.2 原理剖析

因?yàn)镾pring的這個(gè)配置文件和上面的不一樣,它的名字是固定的spring.factories错森,里面的內(nèi)容是key-value形式吟宦,因此一個(gè)文件里可以定義N多個(gè)鍵值對(duì)。我認(rèn)為它比源生JDK的SPI是更加靈活些的~

它主要暴露了兩個(gè)方法:loadFactoriesloadFactoryNames

// @since 3.2
public final class SpringFactoriesLoader {
    ...
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    ...
    // 核心方法如下:
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            // 讀取到資源文件涩维,遍歷
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();

            
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                // 此處使用的是URLResource把這個(gè)資源讀進(jìn)來(lái)~~~
                UrlResource resource = new UrlResource(url);
                
                // 可以看到殃姓,最終它使用的還是PropertiesLoaderUtils,只能使鍵值對(duì)的形式哦~~~ 當(dāng)然xml也是被支持的
                
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
        
                    // 使用逗號(hào),分隔成數(shù)組瓦阐,遍歷   名稱就出來(lái)了~~~
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        } catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
}

四蜗侈、Spring-Boot-Starter

SpringBoot的 @SpringBootApplication 注解,里面還有一個(gè) @EnableAutoConfiguration 注解睡蟋,開啟自動(dòng)配置的踏幻。

可以發(fā)現(xiàn)這個(gè)自動(dòng)配置注解在另一個(gè)工程,而這個(gè)工程里也有個(gè)spring.factories文件戳杀,如下圖:

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggo0tmvup6j30d80c3q3j.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />

我們知道在SpringBoot的目錄下有個(gè)spring.factories文件该面,里面都是一些接口的具體實(shí)現(xiàn)類,可以由SpringFactoriesLoader加載信卡。

那么同樣的道理隔缀,spring-boot-autoconfigure模塊也能動(dòng)態(tài)加載了“剑看一下其中的內(nèi)容:

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggo1gm8smej30le0gyq4p.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />

為了證明這一點(diǎn)猾瘸,我們看一下 dubbo-spring-boot-starter 的結(jié)構(gòu)

<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggo1gm8smej30le0gyq4p.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />

它的關(guān)鍵在于引入了一個(gè) autoconfigure 依賴。這個(gè)里面配置了Spring的 spring.factories 其中只有一個(gè)配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demo</groupId>
    <artifactId>custom-spring-boot-autoconfigure</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>
    </dependencies>

</project>
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
public class CustomAutoConfiguration {

    @Bean
    public CustomConfig customConfig(){
        System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!第三方自定義的配置");
        return new CustomConfig();
    }
}
/**
 * 自定義配置
 */
public class CustomConfig {

    private String value = "Default";
}

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.CustomAutoConfiguration

五、Dubbo SPI

我們首先通過(guò) ExtensionLoader 的 getExtensionLoader 方法獲取一個(gè) ExtensionLoader 實(shí)例牵触,然后再通過(guò) ExtensionLoader 的 getExtension 方法獲取拓展類對(duì)象淮悼。這其中,getExtensionLoader 方法用于從緩存中獲取與拓展類對(duì)應(yīng)的 ExtensionLoader荒吏,若緩存未命中敛惊,則創(chuàng)建一個(gè)新的實(shí)例。該方法的邏輯比較簡(jiǎn)單绰更,本章就不進(jìn)行分析了瞧挤。下面我們從 ExtensionLoader 的 getExtension 方法作為入口,對(duì)拓展類對(duì)象的獲取過(guò)程進(jìn)行詳細(xì)的分析儡湾。

  • 對(duì)Dubbo進(jìn)行擴(kuò)展特恬,不需要改動(dòng)Dubbo的源碼
  • 自定義的Dubbo的擴(kuò)展點(diǎn)實(shí)現(xiàn),是一個(gè)普通的Java類徐钠,Dubbo沒有引入任何Dubbo特有的元素癌刽,對(duì)代碼侵入性幾乎為零。
  • 將擴(kuò)展注冊(cè)到Dubbo中尝丐,只需要在ClassPath中添加配置文件显拜。使用簡(jiǎn)單。而且不會(huì)對(duì)現(xiàn)有代碼造成影響爹袁。符合開閉原則远荠。
  • dubbo的擴(kuò)展機(jī)制設(shè)計(jì)默認(rèn)值:@SPI("dubbo") 代表默認(rèn)的spi對(duì)象
  • Dubbo的擴(kuò)展機(jī)制支持IoC,AoP等高級(jí)功能
  • Dubbo的擴(kuò)展機(jī)制能很好的支持第三方IoC容器,默認(rèn)支持Spring Bean失息,可自己擴(kuò)展來(lái)支持其他容器譬淳,比如Google的Guice。
  • 切換擴(kuò)展點(diǎn)的實(shí)現(xiàn)盹兢,只需要在配置文件中修改具體的實(shí)現(xiàn)邻梆,不需要改代碼。使用方便绎秒。

六浦妄、ServiceLoader類分析

ServiceLoader.class是一個(gè)工具類,根據(jù)META-INF/services/xxxInterfaceName下面的文件名,加載具體的實(shí)現(xiàn)類.

從load(Search.class)進(jìn)去,我們來(lái)扒一下這個(gè)類,下面主要是貼代碼,分析都在代碼注釋內(nèi).

  1. 可以看到,里面并沒有很多邏輯,主要邏輯都交給了LazyIterator這類
 /*
 *入口, 獲取一下當(dāng)前類的類加載器,然后調(diào)用下一個(gè)靜態(tài)方法
 */
 public static <S> ServiceLoader<S> load(Class<S> service) {
     ClassLoader cl = Thread.currentThread().getContextClassLoader();
     return ServiceLoader.load(service, cl);
 }
 /*
 *這個(gè)也沒有什么邏輯,直接調(diào)用構(gòu)造方法
 */
 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
 {
     return new ServiceLoader<>(service, loader);
 }
 /**
 * 也沒有什么邏輯,直接調(diào)用reload
 */
 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();
 }
 /**
 * 直接實(shí)例化一個(gè)懶加載的迭代器
 */
 public void reload() {
     providers.clear();
     lookupIterator = new LazyIterator(service, loader);
 }

  1. LazyIterator這個(gè)迭代器只需要關(guān)心hasNext()和next(), hasNext()里面又只是單純地調(diào)用hasNextService(). 不用說(shuō), next()里面肯定也只是單純地調(diào)用了nextService();
 private boolean hasNextService() {
     if (nextName != null) {
         // nextName不為空,說(shuō)明加載過(guò)了,而且服務(wù)不為空 
         return true;
     }
     // configs就是所有名字為PREFIX + service.getName()的資源
     if (configs == null) {
         try {
             // PREFIX是 /META-INF/services
             // service.getName() 是接口的全限定名稱
             String fullName = PREFIX + service.getName();
             // loader == null, 說(shuō)明是bootstrap類加載器
             if (loader == null)
                 configs = ClassLoader.getSystemResources(fullName);
             else
                 // 通過(guò)名字加載所有文件資源
                 configs = loader.getResources(fullName);
             } catch (IOException x) {
                 fail(service, "Error locating configuration files", x);
             }
     }
     //遍歷所有的資源,pending用于存放加載到的實(shí)現(xiàn)類
     while ((pending == null) || !pending.hasNext()) {
             if (!configs.hasMoreElements()) {
                 //遍歷完所有的文件了,直接返回
                 return false;
             }
             
             // parse方法主要調(diào)用了parseLine,功能:
             // 1. 分析每個(gè)PREFIX + service.getName() 目錄下面的所有文件
             // 2. 判斷每個(gè)文件是否是合法的java類的全限定名稱,如果是就add到pending變量中
             pending = parse(service, configs.nextElement());
     }
     // 除了第一次進(jìn)來(lái),后面每次調(diào)用都是直接到這一步了
     nextName = pending.next();
     return true;
 }

  1. 再來(lái)看看nextService干了啥
 private S nextService() {
     // 校驗(yàn)一下
     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");
     }
     // 是不是service的子類,或者同一個(gè)類
     if (!service.isAssignableFrom(c)) {
         fail(service,"Provider " + cn  + " not a subtype");
     }
     try {
         // 實(shí)例化這個(gè)類, 然后向上轉(zhuǎn)一下
         S p = service.cast(c.newInstance());
         // 緩存起來(lái),避免重復(fù)加載
         providers.put(cn, p);
         return p;
     } catch (Throwable x) {
         fail(service,"Provider " + cn + " could not be instantiated",x);
     }
     throw new Error();          // This cannot happen
 }

https://cloud.tencent.com/developer/article/1497777

https://juejin.im/post/5d9998d5f265da5bab5bc0f2#heading-5

https://www.cnblogs.com/itplay/p/9927892.html

https://www.cnblogs.com/LUA123/p/12460869.html

https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

https://www.cnblogs.com/GrimMjx/p/10970643.html

https://my.oschina.net/j4love/blog/1813040

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市替裆,隨后出現(xiàn)的幾起案子校辩,更是在濱河造成了極大的恐慌,老刑警劉巖辆童,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惠赫,居然都是意外死亡把鉴,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)庭砍,“玉大人场晶,你說(shuō)我怎么就攤上這事〉「祝” “怎么了诗轻?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)揭北。 經(jīng)常有香客問(wèn)我扳炬,道長(zhǎng),這世上最難降的妖魔是什么搔体? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任恨樟,我火速辦了婚禮,結(jié)果婚禮上疚俱,老公的妹妹穿的比我還像新娘劝术。我一直安慰自己,他們只是感情好呆奕,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布养晋。 她就那樣靜靜地躺著,像睡著了一般梁钾。 火紅的嫁衣襯著肌膚如雪绳泉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天陈轿,我揣著相機(jī)與錄音圈纺,去河邊找鬼。 笑死麦射,一個(gè)胖子當(dāng)著我的面吹牛蛾娶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播潜秋,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蛔琅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了峻呛?” 一聲冷哼從身側(cè)響起罗售,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钩述,沒想到半個(gè)月后寨躁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牙勘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年职恳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了所禀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡放钦,死狀恐怖色徘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情操禀,我是刑警寧澤褂策,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站颓屑,受9級(jí)特大地震影響斤寂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邢锯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一扬蕊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丹擎,春花似錦尾抑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至护戳,卻和暖如春翎冲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳荒。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工抗悍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钳枕。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓缴渊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鱼炒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衔沼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361