一篇文章帶你理解Java的SPI機制(圖文并茂)
一、Java的SPI機制
1、什么是Java的SPI ?
SPI全稱 Service Provider Interface ,字面意思:“服務(wù)提供者的接口”,是一種服務(wù)發(fā)現(xiàn)機制。
用于實現(xiàn)框架或庫的擴展點,允許在運行時動態(tài)地插入或更換組件實現(xiàn)。它提供了一個框架來發(fā)現(xiàn)和加載服務(wù)實現(xiàn),使得軟件模塊能夠靈活地選擇和使用不同的服務(wù)提供商。SPI鼓勵松耦合的設(shè)計,因為服務(wù)的消費者不需要直接依賴于具體的服務(wù)實現(xiàn)。有沒有想到面向?qū)ο蟮哪硞€設(shè)計原則,SOLID中的 O 開閉原則,對擴展開放對修改關(guān)閉。Java允許服務(wù)提供者,按照SPI給定的規(guī)則實現(xiàn)自己的服務(wù),而Java應(yīng)用使用通過SPI規(guī)則提供的服務(wù)時無需進(jìn)行額外的配置,并且可以隨時替換服務(wù),也無需修改業(yè)務(wù)代碼。
SPI可以和我們常用的API對比著理解。
API全稱、Application Programming Interface,字面意思:“應(yīng)用程序編程接口” 。API是一組規(guī)則和定義,允許一個軟件應(yīng)用與另一個軟件應(yīng)用、庫或操作系統(tǒng)進(jìn)行交互。它定義了如何進(jìn)行數(shù)據(jù)傳輸、請求服務(wù)或執(zhí)行特定功能的協(xié)議和工具。API為開發(fā)人員提供了一種標(biāo)準(zhǔn)化的方式來訪問和使用預(yù)先構(gòu)建的功能,而無需了解這些功能內(nèi)部的復(fù)雜實現(xiàn)細(xì)節(jié)。
總結(jié):
| 名稱 | 目的 | 使用者 | 舉例 |
|---|---|---|---|
| SPI | 支持可插拔的架構(gòu),便于組件和服務(wù)的替換與擴展。 | 主要由服務(wù)提供者(如庫、框架開發(fā)者)實現(xiàn),但也需要應(yīng)用開發(fā)者配置以啟用特定的服務(wù)實現(xiàn)。(這里說的配置一般就是引入jar或者maven、gradle的坐標(biāo)即可) | Java中,JDBC驅(qū)動的加載就是一個典型的SPI應(yīng)用。 |
| API | 簡化開發(fā)過程,提高效率,促進(jìn)不同系統(tǒng)間的互操作性。 | 通常由應(yīng)用開發(fā)者使用,以集成外部服務(wù)或內(nèi)部模塊。 | 語音識別 API、文件上傳 API等 |
個人感覺SPI就是種"設(shè)計模式",只是不在常見的23種設(shè)計模式之中,Java利用"SPI這種設(shè)計模式" 實現(xiàn)靈活的選擇服務(wù)供應(yīng)商實現(xiàn),來達(dá)到解耦的目的,以此提高程序的可修改性和靈活性。
SPI和外觀設(shè)計模式也有相通之處、外觀模式(Facade) 定義了一個高層接口,為子系統(tǒng)中的一組接口提供一個一致的界面,從而簡化子系統(tǒng)的使用。
簡單的圖示對比:拿NASA的重返月球計劃舉例。
NASA準(zhǔn)備把重返月球計劃的著陸器設(shè)計部分分包給商業(yè)航天公司來完成。
如果還按照API的方式來實現(xiàn),那么可能就是下圖中的結(jié)果。NASA想替換某個方案就必須修改系統(tǒng)去適配其他公司的API接口。
這樣代碼的耦合性就大大提高了。并且導(dǎo)致NASA失去了主動性。NASA這個甲方才不愿意這么干。 所以NASA決定采用SPI規(guī)則來約束供應(yīng)商。

NASA給出了關(guān)鍵性的技術(shù)指標(biāo)。
假設(shè)目前有SpaceX 和 Blue Origin兩家公司中標(biāo) 去完成著陸器的設(shè)計。 這兩家公司需要遵循NASA給定的SPI規(guī)則進(jìn)行設(shè)計。
兩家公司月球著陸器產(chǎn)品設(shè)計完成后,NASA只需要按照SPI規(guī)則去加載對應(yīng)的產(chǎn)品即可。

下圖可以看出,雖然兩家公司的火箭設(shè)計不一樣,但是他們的接口都符合NASA指定的接口規(guī)則。這樣NASA就可以很輕松的集成兩家公司的方案,如果覺得SpaceX的方案不好,直接拿Blue Origin的方案無縫替代即可。

2、JavaSPI 代碼示例 (使用Maven項目演示)
①、NASA先定義SPI接口 并發(fā)布到本地倉庫供SpaceX和BlueOrigin實現(xiàn)項目結(jié)構(gòu):

pom文件
<?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.nasa</groupId>
<artifactId>SPI-interface</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
maven打包安裝到本地倉庫

打包好的jar名稱

SPI接口:
package com.nasa;
/**
* NASA提供的SPI接口
* */
public interface LandingOnTheMoon {
/**
* 著陸方法
*/
void land();
}SPI加載實現(xiàn)(稍后會詳細(xì)介紹):
package com.nasa;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
/**
* 加載具體的服務(wù)實現(xiàn)
* */
public class LandingOnTheMoonLoader {
private static volatile LandingOnTheMoonLoader LOADER;
private final LandingOnTheMoon landingOnTheMoon;
private final List<LandingOnTheMoon> landingOnTheMoons;
/**
* 加載服務(wù)
* */
private LandingOnTheMoonLoader() {
ServiceLoader<LandingOnTheMoon> loader = ServiceLoader.load(LandingOnTheMoon.class);
List<LandingOnTheMoon> list = new ArrayList<>();
for (LandingOnTheMoon landingOnTheMoon : loader) {
list.add(landingOnTheMoon);
}
landingOnTheMoons = list;
if (!list.isEmpty()) {
// 取第一個
landingOnTheMoon = list.get(0);
} else {
landingOnTheMoon = null;
}
}
/**
* LandingOnTheMoonLoader 單例加載
* */
public static LandingOnTheMoonLoader getLOADER() {
if (LOADER == null) {
synchronized (LandingOnTheMoonLoader.class) {
if (LOADER == null) {
LOADER = new LandingOnTheMoonLoader();
}
}
}
return LOADER;
}
public void land(){
if(landingOnTheMoons.isEmpty()){
System.out.println("LandingOnTheMoon服務(wù)未加載!");
}else {
LandingOnTheMoon landingOnTheMoon = landingOnTheMoons.get(0);
landingOnTheMoon.land();
}
}
}②、SpaceX實現(xiàn)自己的登陸月球方案
先引入NASA 指定的SPI接口
pom文件
<?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.spacex</groupId>
<artifactId>SpaceX</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.nasa</groupId>
<artifactId>SPI-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
SpaceX的登陸月球?qū)崿F(xiàn):
package com.spacex;
import com.nasa.LandingOnTheMoon;
/**
* SpaceX實現(xiàn)的月球著陸方案
* */
public class SpaceXLand implements LandingOnTheMoon {
@Override
public void land() {
System.out.println("SpaceX landing on the moon with StarShip~");
}
}SpaceX的登陸月球?qū)崿F(xiàn)配置信息:
注意: Maven項目中 META-INF/services/SPI接口全限定名 需要放在resources目錄下

內(nèi)容為SpaceX實現(xiàn)類的全限定名 :

③、BlueOrigin實現(xiàn)自己的登陸月球方案
先引入NASA 指定的SPI接口
pom文件
<?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.blue</groupId>
<artifactId>BlueOrigin</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.nasa</groupId>
<artifactId>SPI-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
BlueOrigin的登陸月球?qū)崿F(xiàn):
package com.blue;
import com.nasa.LandingOnTheMoon;
/**
* 藍(lán)色起源實現(xiàn)的月球著陸方案
* */
public class BlueOriginLand implements LandingOnTheMoon {
@Override
public void land() {
System.out.println("BlueOrigin landing on the moon with NewGlenn~");
}
}
BlueOrigin的登陸月球?qū)崿F(xiàn)配置信息:Maven項目中 META-INF/services/SPI接口全限定名 需要放在resources目錄下

內(nèi)容為BlueOrigin實現(xiàn)類的全限定名 :

編寫測試代碼:
新建一個Maven項目
pom文件中引入SpaceX的實現(xiàn)坐標(biāo)
<?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.nasa</groupId>
<artifactId>NASA-LANDING</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--使用藍(lán)色起源的著陸實現(xiàn)-->
<!-- <dependency>
<groupId>com.blue</groupId>
<artifactId>BlueOrigin</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>-->
<!--使用SpaceX的著陸實現(xiàn)-->
<dependency>
<groupId>com.spacex</groupId>
<artifactId>SpaceX</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
package com.nasa;
public class LandingShow {
public static void main(String[] args) {
LandingOnTheMoonLoader service = LandingOnTheMoonLoader.getLOADER();
service.land();
}
}執(zhí)行結(jié)果:
SpaceX landing on the moon with StarShip~
此時如果NASA覺得SpaceX的方案不行,需要更換BlueOrigin的方案,操作起來非常簡單,只需要注釋掉SpaceX的坐標(biāo),添加BlueOrigin的坐標(biāo),業(yè)務(wù)代碼完全不用改。
<?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.nasa</groupId>
<artifactId>NASA-LANDING</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--使用藍(lán)色起源的著陸實現(xiàn)-->
<dependency>
<groupId>com.blue</groupId>
<artifactId>BlueOrigin</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--使用SpaceX的著陸實現(xiàn)-->
<!-- <dependency>-->
<!-- <groupId>com.spacex</groupId>-->
<!-- <artifactId>SpaceX</artifactId>-->
<!-- <version>1.0-SNAPSHOT</version>-->
<!-- </dependency>-->
</dependencies>
</project>
執(zhí)行結(jié)果:
BlueOrigin landing on the moon with NewGlenn~
3、 JavaSPI 機制的核心-ServiceLoader
上面代碼中我們使用ServiceLoader 去加載具體的服務(wù)實現(xiàn)。
ServiceLoader 是從JDK1.6 開始提供的一個類,用于加載服務(wù)提供者。

我們看下源碼:
其中 String PREFIX = “META-INF/services/”;
這個就是JDK的SPI功能規(guī)定的具體服務(wù)實現(xiàn)的配置信息文件所在的目錄 META-INF/services/
JDK的SPI規(guī)定 服務(wù)實現(xiàn)者需要在 META-INF/services/ 目錄下 新建文件名為 SPI接口全限定類名的文件
文件內(nèi)容為 服務(wù)實現(xiàn)者需要被加載的具體類的全限定類名

我們上面代碼在加載服務(wù)的時候調(diào)用了ServiceLoader.load(LandingOnTheMoon.class);方法
ServiceLoader<LandingOnTheMoon> loader = ServiceLoader.load(LandingOnTheMoon.class);
看下這個方法的源碼:
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
可以看到這個方法 只是獲取了當(dāng)前線程的上下文類加載器 然后又調(diào)用了一個load的重載方法
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
在重載的load方法中又調(diào)用了ServiceLoader的私有構(gòu)造器
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();
}
然后調(diào)用 reload();方法
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
reload();方法里面 調(diào)用了 new LazyIterator(service, loader);
具體的類加載行為就是在LazyIterator內(nèi)部類中完成的。
這里的 lookupIterator = new LazyIterator(service, loader); 創(chuàng)建LazyIterator 懶加載迭代器對象,并沒有馬上去加載SPI的具體服務(wù)。
當(dāng)我們調(diào)用 for (LandingOnTheMoon landingOnTheMoon : loader) {…} 循環(huán)時,會觸發(fā)迭代器,在ServiceLoader中,迭代器是由Iterable接口的iterator()方法提供的。當(dāng)?shù)谝淮握{(diào)用iterator()時,如果之前沒有創(chuàng)建過迭代器,它會創(chuàng)建一個新的LazyIterator實例。
對應(yīng)下面的源碼:
// lookupIterator 在 調(diào)用reload 方法時 創(chuàng)建過了
private LazyIterator lookupIterator;
// 存儲已經(jīng)加載過的服務(wù)提供者實例 在 調(diào)用reload 方法時 調(diào)用了providers.clear(); 清空了這個集合
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public Iterator<S> iterator() {
return new Iterator<S>() {
// 第一次調(diào)用時 providers 是空集合 會調(diào)用 lookupIterator 也就是 LazyIterator 的實例
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();
}
};
}下面看 LazyIterator 的實現(xiàn) 具體的類加載就在這里:
LazyIterator 的 hasNext 和 next方法 中判斷了 acc 是否是null
其中 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 這段代碼是在 ServiceLoader初始化的時候執(zhí)行的
主要用于判斷 當(dāng)前Java運行環(huán)境中是否存在安全管理器(SecurityManager),默認(rèn)情況下不會配置,如果配置了SecurityManager,某些敏感操作(比如文件訪問、網(wǎng)絡(luò)連接等)可能會受到安全策略的限制或需要相應(yīng)的權(quán)限檢查。
這個不重要,我們還是看具體的方法 hasNextService 和 nextService方法。
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);
}
}
LazyIterator 的 hasNextService 和 nextService方法終于到了加載具體服務(wù)的地方了。
// 迭代器
Iterator<String> pending = null;
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 這里獲取 SPI配置文件的文件名 包含 META-INF/services/
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()) {
// 配置內(nèi)也沒有值
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置內(nèi)的 具體服務(wù)名稱
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// nextName 在hasNextService中已經(jīng)被賦值了
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 關(guān)鍵點終于來了 最終還是利用Java的反射機制 完成類的加載
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
// 判斷下 加載的 Class 的類型 是否實現(xiàn)了 給定的 SPI接口 拿上面NASA登月的例子看,這里的 service 就是 LandingOnTheMoon.class
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 通過反射實例化 SPI的實現(xiàn)類 并且轉(zhuǎn)換成 SPI接口類型
S p = service.cast(c.newInstance());
// 保存到 LinkedHashMap<String,S> providers 緩存中
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}看 具體的parse 方法(解析服務(wù)實現(xiàn)者提供的配置文件)
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
// 保存解析配置文件內(nèi)的 全限定類名
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
// 一行一行的讀取配置信息 并將每行解析出的全限定類名 保存到 names
// parseLine 方法不用看了 就是解析字符串的
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);
}
}
// 返回 擁有所有實現(xiàn)SPI接口的服務(wù)提供者全限定類名集合的迭代器
return names.iterator();
}繞了半天 通過源碼發(fā)現(xiàn) 最終類的加載還是通過 Java反射機制實現(xiàn)的。
那了解了ServiceLoader的具體實現(xiàn)之后,我們也可以實現(xiàn)一個自己的 服務(wù)加載器。
ServiceLoader只不過在實現(xiàn)了核心的SPI服務(wù)加載功能的基礎(chǔ)上增加了一些額外的功能,比如通過LazyIterator 實現(xiàn)延遲加載,
可以調(diào)用reload() 在應(yīng)用運行時實現(xiàn)服務(wù)的動態(tài)加載(不用重啟應(yīng)用就能加載服務(wù)),訪問控制(比如判斷 當(dāng)前Java運行環(huán)境中是否存在安全管理器),還有按照加載順序保存已加載的服務(wù)等等。不過ServiceLoader也是線程不安全的這點需要注意。
4、實現(xiàn)自己的ServiceLoader
我們實現(xiàn)一個最簡單的ServiceLoader只考慮加載服務(wù)的功能,其他的安全、性能之類的都不考慮。
我們也仿照ServiceLoader類的結(jié)構(gòu),和SPI配置類規(guī)定的路徑來實現(xiàn)。
package com.nasa;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;
public class MySimpleServiceLoader<T> {
// 直接copy ServiceLoader的規(guī)則
private static final String PREFIX = "META-INF/services/";
// 定義的SPI接口
private final Class<T> service;
// 類加載器
private final ClassLoader loader;
// 保存已加載的服務(wù)實例
private final LinkedHashMap<String, T> providers = new LinkedHashMap<>();
// 構(gòu)造方法T
private MySimpleServiceLoader(Class<T> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 加載服務(wù)
realLoad();
}
public static <T> MySimpleServiceLoader<T> load(Class<T> service) {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
return new MySimpleServiceLoader<>(service, contextClassLoader);
}
private void realLoad() {
String fullName = PREFIX + service.getName();
try {
// 獲取配置文件
Enumeration<URL> resources = loader.getResources(fullName);
// 讀取配置文件
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
InputStream inputStream = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
// 我們就不做解析錯誤之類的邏輯了 默認(rèn)給的配置文件都是對的
String name = reader.readLine();
// 通過反射創(chuàng)建對象
Class<?> aClass = Class.forName(name, false, loader);
// 判斷 服務(wù)實現(xiàn)類是否實現(xiàn)了 給定的SPI接口
if (service.isAssignableFrom(aClass)) {
T cast = service.cast(aClass.newInstance());
providers.put(name, cast);
}
}
} catch (Exception e) {
System.out.println("出現(xiàn)異常!");
e.printStackTrace();
}
}
// 返回加載好的 服務(wù)實現(xiàn)
public LinkedHashMap<String, T> getProviders() {
return providers;
}
}使用自定義的服務(wù)加載器 MySimpleServiceLoader
package com.nasa;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 加載具體的服務(wù)實現(xiàn)
* */
public class LandingOnTheMoonLoader {
private static volatile LandingOnTheMoonLoader LOADER;
private final LandingOnTheMoon landingOnTheMoon;
private final List<LandingOnTheMoon> landingOnTheMoons = new ArrayList<>();
/**
* 加載服務(wù)
* */
private LandingOnTheMoonLoader() {
// 通過自定義的服務(wù)加載器 去加載服務(wù)實現(xiàn)
MySimpleServiceLoader<LandingOnTheMoon> mySimpleServiceLoader = MySimpleServiceLoader.load(LandingOnTheMoon.class);
LinkedHashMap<String, LandingOnTheMoon> providers = mySimpleServiceLoader.getProviders();
providers.forEach((k,v)->{
System.out.println(k);
landingOnTheMoons.add(v);
});
if (!landingOnTheMoons.isEmpty()) {
// 取第一個
landingOnTheMoon = landingOnTheMoons.get(0);
} else {
landingOnTheMoon = null;
}
}
/**
* LandingOnTheMoonLoader 單例加載
* */
public static LandingOnTheMoonLoader getLOADER() {
if (LOADER == null) {
synchronized (LandingOnTheMoonLoader.class) {
if (LOADER == null) {
LOADER = new LandingOnTheMoonLoader();
}
}
}
return LOADER;
}
public void land(){
if(landingOnTheMoons.isEmpty()){
System.out.println("LandingOnTheMoon服務(wù)未加載!");
}else {
LandingOnTheMoon landingOnTheMoon = landingOnTheMoons.get(0);
landingOnTheMoon.land();
}
}
}測試:
public static void main(String[] args) {
LandingOnTheMoonLoader service = LandingOnTheMoonLoader.getLOADER();
service.land();
}
使用SpaceX的實現(xiàn),結(jié)果如下:
com.spacex.SpaceXLand
SpaceX landing on the moon with StarShip~
5、Java中還有哪些SPI實現(xiàn)?
1、JDBC (Java Database Connectivity): JDBC驅(qū)動的加載就是SPI的一個經(jīng)典應(yīng)用。各個數(shù)據(jù)庫廠商提供自己的JDBC驅(qū)動實現(xiàn),應(yīng)用程序無需直接引用具體驅(qū)動的實現(xiàn)類,只需將驅(qū)動JAR包放入類路徑,Java SPI機制會自動發(fā)現(xiàn)并加載合適的驅(qū)動。
2、日志框架: 如SLF4J (Simple Logging Facade for Java) 和Logback、Log4j等,它們利用SPI機制來發(fā)現(xiàn)和加載具體的日志實現(xiàn)。用戶可以根據(jù)需要選擇或更換日志實現(xiàn),而無需修改應(yīng)用程序代碼。
3、Spring框架: 雖然Spring框架本身更傾向于使用其自身的bean工廠和依賴注入機制來管理組件和服務(wù),但Spring也支持SPI機制,特別是在某些擴展點和與第三方庫集成時。
4、Dubbo: Apache Dubbo是一個高性能的RPC框架,它大量使用SPI機制來實現(xiàn)其插件體系,允許用戶輕松替換或擴展序列化、負(fù)載均衡、集群容錯等核心組件。
5、JNDI (Java Naming and Directory Interface): JNDI服務(wù)提供者也是通過SPI機制注冊和發(fā)現(xiàn)的,允許應(yīng)用程序訪問不同的命名和目錄服務(wù)。
6、JAX-WS (Java API for XML Web Services) 和 JAX-RS (Java API for RESTful Web Services): 這些Java Web服務(wù)技術(shù)框架使用SPI來發(fā)現(xiàn)和加載實現(xiàn)特定功能的服務(wù)提供者,比如SOAP綁定和HTTP連接器。
7、JavaBeans Activation Framework (JAF): 用于處理MIME類型的郵件附件等,通過SPI來發(fā)現(xiàn)數(shù)據(jù)處理器。
8、JavaMail: 用于發(fā)送和接收電子郵件的API,利用SPI來加載傳輸協(xié)議和其他服務(wù)提供者。
…
總結(jié)
到此這篇關(guān)于Java的SPI機制的文章就介紹到這了,更多相關(guān)Java的SPI機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java8使用Supplier啟動ScheduledThread代碼實例
這篇文章主要介紹了Java8使用Supplier啟動ScheduledThread詳解,項目開啟立即啟動定時任務(wù)是很多項目都會遇到的一個需求,如何利用Java提供的函數(shù)優(yōu)雅的寫出來十分考驗一個人的功底,需要的朋友可以參考下2024-01-01
bug解決Failed_to_execute_goal_org.springframework
這篇文章主要為大家介紹了bug解決Failed_to_execute_goal_org.springframework,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
IDEA安裝部署Alibaba Cloud Toolkit的實現(xiàn)步驟
Alibaba Cloud Toolkit是阿里云針對IDE平臺為開發(fā)者提供的一款插件,本文主要介紹了IDEA安裝部署Alibaba Cloud Toolkit的實現(xiàn)步驟,具有一定的參考價值,感興趣的可以了解一下2023-08-08
Spring5.2.x 源碼本地環(huán)境搭建的方法步驟
這篇文章主要介紹了Spring5.2.x 源碼本地環(huán)境搭建的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

