天天看點

從源碼全面解析 Java SPI 的來龍去脈

作者:Java架構學習指南

一、引言

對于 Java 開發者而言,關于 dubbo ,我們一般當做黑盒來進行使用,不需要去打開這個黑盒。

但随着目前程式員行業的發展,我們有必要打開這個黑盒,去探索其中的奧妙。

雖然現在是網際網路寒冬,但乾坤未定,你我皆是黑馬!

廢話不多說,發車!

二、SPI是什麼

SPI 全稱 Service Provider Interface ,是 Java 提供的一套用來被第三方實作或者擴充的 API,它可以用來啟用架構擴充和替換元件。

從源碼全面解析 Java SPI 的來龍去脈

Java SPI 實際上是 基于接口的程式設計+政策模式+配置檔案 組合實作的動态加載機制。

Java SPI 就是提供這樣的一個機制:為某個接口尋找服務實作的機制。

将裝配的控制權移到程式之外,在子產品化設計中這個機制尤其重要。

是以 SPI 的核心思想就是解耦。

三、使用介紹

我們定義一個接口 Phone:

package com.study.spring.spi;
public interface Phone {
    public void getName();
}           

實作其三個類:

  • HuaWei
package com.study.spring.spi;

public class HuaWei implements Phone {
    @Override
    public void getName() {
        System.out.println("我是是華為手機");
    }
}           
  • IPhone
package com.study.spring.spi;

public class IPhone implements Phone {
    @Override
    public void getName() {
        System.out.println("我是是蘋果手機");
    }
}           
  • XiaoMi
package com.study.spring.spi;

public class XiaoMi implements Phone {
    @Override
    public void getName() {
        System.out.println("我是是小米手機");
    }
}           

重點來了:我們要在 resources 檔案夾下面建立一個路徑:META-INF/services

然後我們建立一個 txt 名為:com.study.spring.spi.Phone,如下:

從源碼全面解析 Java SPI 的來龍去脈

我們在這個檔案中寫上各實作類的路徑:

com.study.spring.spi.HuaWei
com.study.spring.spi.IPhone
com.study.spring.spi.XiaoMi           

測試類:

package com.study.spring.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class JavaSPITest {
    public static void main(String[] args) {
        // 執行Java SPI的規範
        ServiceLoader<Phone> phoneServiceLoader = ServiceLoader.load(Phone.class);
		// 擷取疊代器
        Iterator<Phone> it = phoneServiceLoader.iterator();
        // 疊代周遊,輸出集合中的所有元素
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}           

結果:

我是是華為手機
我是是蘋果手機
我是是小米手機           

從這裡我們可以看到,通過 SPI 的機制,我們可以擷取目前接口類的實作

四、生産場景

相信大家在生産上都使用過 JDBC,沒錯,我們的 JDBC 實際上也使用了 SPI

我們看 DriverManager 的靜态方法 loadInitialDrivers:

static {
     // 初始化加載
     loadInitialDrivers();
     println("JDBC DriverManager initialized");
}           

我們檢視下 loadInitialDrivers 方法的代碼:

private static void loadInitialDrivers() {
    String drivers;
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // SPI的加載機制
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            // 疊代
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            }
            return null;
        }
    });   
}           

當然,這裡我們需要引入下面的 MAVEN 依賴,不然 Driver.class 的實作類為空

<dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.18</version>
</dependency>           

測試類:

package com.study.spring.spi;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

public class JDBCTest {
    public static void main(String[] args) {
        ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);

        for (Iterator<Driver> iterator = serviceLoader.iterator(); iterator.hasNext(); ) {

            Driver driver = iterator.next();

            System.out.println(driver.getClass().getPackage() + " ------> " + driver.getClass().getName());
        }
    }
}           

啟動:

package com.mysql.cj.jdbc, JDBC, version 4.2 ------> com.mysql.cj.jdbc.Driver           

從這裡的輸出我們可以得出一個假設:我們引入的 JDBC 包裡面,存在上述 SPI 機制的 txt 檔案名稱為: java.sql.Driver 且内容為:com.mysql.cj.jdbc.Driver

我們直接去引入的包裡面搜尋一下:

從源碼全面解析 Java SPI 的來龍去脈

果然沒錯,接下來我們來一下 SPI 的原理

五、SPI運作原理剖析

從我們上面的示例中可以發現,SPI 的實作一共兩行:

package com.study.spring.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class JavaSPITest {
    public static void main(String[] args) {
        // 執行Java SPI的規範
        ServiceLoader<Phone> phoneServiceLoader = ServiceLoader.load(Phone.class);
		// 擷取疊代器
        Iterator<Phone> it = phoneServiceLoader.iterator();
        // 疊代周遊,輸出集合中的所有元素
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}           

我們挨個分析

1、服務初始化加載

ServiceLoader<Phone> phoneServiceLoader = ServiceLoader.load(Phone.class);

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 擷取目前線程的類加載器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
    return new ServiceLoader<>(service, loader);
}

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    // svc:com.study.spring.spi.Phone
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    // AppClassLoader
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    // 安全控制
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    // 重點
    reload();
}           

這裡的重點還是在我們的 reload 中:

public void reload() {
    // 清空我們的服務提供者緩存
    providers.clear();
    // 延時疊代器,用于延遲加載服務提供者
    // 達到用需加載的目的
    lookupIterator = new LazyIterator(service, loader);
}

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

2、擷取疊代器

Iterator<Phone> it = phoneServiceLoader.iterator();

// 傳回我們ServiceLoader自定義實作的疊代器
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();
        }
    };
}           

3、判斷是否有資料

it.hasNext()
public boolean hasNext() {
    // 剛剛初始化knownProviders是空的
    if (knownProviders.hasNext())
        return true;
    // 
    return lookupIterator.hasNext();
}

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);
    }
}           

重點還是在 hasNextService 方法中

private boolean hasNextService() {
    // 如果目前的Name不等于空直接傳回
    if (nextName != null) {
        return true;
    }
    // 配置是否為空(第一次肯定為空)
    if (configs == null) {
        try {
            // 寫死的:META-INF/services 加上接口的名字
            // META-INF/services/com.study.spring.spi.Phone
            String fullName = PREFIX + service.getName();
            // 加載配置
            // configs
            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;
        }
        // 從上述配置檔案中讀取目前的配置資訊
        // com.study.spring.spi.HuaWei
        // com.study.spring.spi.IPhone
        // com.study.spring.spi.XiaoMi
        pending = parse(service, configs.nextElement());
    }
    // 周遊目前的清單,傳回第一次的周遊資訊(com.study.spring.spi.HuaWei)
    nextName = pending.next();
    return true;
}           

4、結果輸出

System.out.println(it.next());
public S next() {
    // 剛剛初始化knownProviders是空的
    if (knownProviders.hasNext())
        return knownProviders.next().getValue();
    return lookupIterator.next();
}

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);
    }
}           

重點還是在 nextService 方法中

private S nextService() {
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    // cn:com.study.spring.spi.HuaWei
    // 根據類加載器+類的名稱得出類
    c = Class.forName(cn, false, loader);
	
    // 建立執行個體 
    S p = service.cast(c.newInstance());
    // 将執行個體放到緩存裡面
    // key:com.study.spring.spi.HuaWei
    // value:HuaWei@773
    providers.put(cn, p);
    // 傳回執行個體
    return p;
}           

六、SPI流程圖

從源碼全面解析 Java SPI 的來龍去脈

七、總結

魯迅先生曾說:獨行難,衆行易,和志同道合的人一起進步。彼此毫無保留的分享經驗,才是對抗網際網路寒冬的最佳選擇。

其實很多時候,并不是我們不夠努力,很可能就是自己努力的方向不對,如果有一個人能稍微指點你一下,你真的可能會少走幾年彎路。

繼續閱讀