JAVA的SPI機(jī)制
JAVA的SPI機(jī)制是什么?
SPI(Service Provider Interface): ,“服務(wù)提供者接口”,是指在 服務(wù)使用方 角度提出的“接口要求”,是對(duì)“服務(wù)提供方”提出的約定,簡(jiǎn)單說(shuō)就是:“我需要這樣的服務(wù),現(xiàn)在你們來(lái)滿足”。
是不是看起來(lái)很高深的樣子.其實(shí)特別簡(jiǎn)單 我們先來(lái)一個(gè)例子,大家更容易理解什么是JAVA的SPI機(jī)制:
首先我們提供了一個(gè)訂單處理的接口
/**
?*?@Author:?lty
?*?@Date:?2021/1/22?14:24
?*?訂單的處理接口
?*/
public?interface?OrderHandlerService?{
????String??handler(String?orderid);
}
兩個(gè)實(shí)現(xiàn)類
public?class?PddOrderHandler?implements?OrderHandlerService
{
????@Override
????public?String?handler(String?orderid)?{
????????System.out.println("Pdd?handler?execute");
????????return?"Pdd?handler?execute";
????}
}
public?class?TaobaoOrderHandler?implements?OrderHandlerService
{
????@Override
????public?String?handler(String?orderid)?{
????????System.out.println("taobao?handler?execute");
????????return?"taobao?handler?execute";
????}
}
類圖關(guān)系:

1. 通過(guò)直接調(diào)用實(shí)現(xiàn)類的方式
平常在開(kāi)發(fā)過(guò)程中 我們實(shí)例一個(gè)對(duì)象是通過(guò)new 的方式
public?static?void?main(String[]?args)?{
????????OrderHandlerService?pddOrderHandler?=?new?PddOrderHandler();
????????pddOrderHandler.handler("000000001");
}
????????
//Pdd?handler?execute
2. 通過(guò)SPI 提供實(shí)現(xiàn)類的方式
通過(guò)ServiceLoader.load()方法獲取實(shí)現(xiàn)類
public?static?void?main(String[]?args)?{
????????//使用spi
????????ServiceLoader?services?=?ServiceLoader.load(OrderHandlerService.class);
????????services.forEach(orderHandlerService?->?{
????????????orderHandlerService.handler("000001");
????????});
????????
????????//Pdd?handler?execute
????????//taobao?handler?execute
?}
注意:
通過(guò)SPI方式 我們需要提供一個(gè)特別的文件:
文件位于 /resources/META-INF/services
文件名為 com.xxx. 即接口的全限定名稱。

內(nèi)容為兩個(gè)實(shí)現(xiàn)類的全限定名稱:
com.liangtengyu.service.Impl.PddOrderHandler
com.liangtengyu.service.Impl.TaobaoOrderHandler
具體的底層實(shí)現(xiàn)可以分離出來(lái) 實(shí)現(xiàn)外部加載,也可以將每組實(shí)現(xiàn)和SPI配置文件打包成不同的jar,在具體使用時(shí)根據(jù)需要使用不同的jar即可。
在源碼中
ServiceLoader類定義了一個(gè)前綴?
private static final String PREFIX = "META-INF/services/"
用來(lái)約定上述指定的位置,基于約定的配置讀取會(huì)從這里查找,如果我們引入了第三方的jar包,如果jar中的META-INF/service有OrderHandlerService的實(shí)現(xiàn),也會(huì)被讀取,并且實(shí)例化里面的類。
SPI的應(yīng)用
這里我們以JDBC為例子
mysql-connector-java:5.1.32 包的 META-INF/services/ 目錄下有個(gè) java.sql.Driver

文件,內(nèi)容為:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
在SqlLite中 也有同樣的文件.
它們都是用來(lái)加載實(shí)現(xiàn)了java.sql.Driver接口實(shí)現(xiàn)類的位置

//內(nèi)容為
org.sqlite.JDBC
其它的應(yīng)用:
日志門(mén)面接口實(shí)現(xiàn)類加載,SLF4J加載不同提供商的日志實(shí)現(xiàn)類
Spring中大量使用了SPI,比如:對(duì)servlet3.0規(guī)范對(duì)ServletContainerInitializer的實(shí)現(xiàn)、自動(dòng)類型轉(zhuǎn)換Type Conversion SPI(Converter SPI、Formatter SPI)等
Dubbo中也大量使用SPI的方式實(shí)現(xiàn)框架的擴(kuò)展, 不過(guò)它對(duì)Java提供的原生SPI做了封裝,允許用戶擴(kuò)展實(shí)現(xiàn)Filter接口
...
總結(jié)
優(yōu)點(diǎn):使用Java SPI機(jī)制的優(yōu)勢(shì)是實(shí)現(xiàn)解耦,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離,而不是耦合在一起。應(yīng)用程序可以根據(jù)實(shí)際業(yè)務(wù)情況啟用框架擴(kuò)展或替換框架組件。
缺點(diǎn):雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過(guò)遍歷全部獲取,也就是接口的實(shí)現(xiàn)類全部加載并實(shí)例化一遍。如果你并不想用某些實(shí)現(xiàn)類,它也被加載并實(shí)例化了,這就造成了浪費(fèi)。獲取某個(gè)實(shí)現(xiàn)類,的方式不夠靈活,多個(gè)并發(fā)多線程使用ServiceLoader類的實(shí)例是不安全的。
掃描二維碼
獲取更多精彩
JAVA寶典
