Java的SPI機制以及基于SPI編程示例詳解
背景
在面向?qū)ο蟮脑O(shè)計原則中,一般推薦模塊之間基于接口編程,通常情況下調(diào)用方模塊是不會感知到被調(diào)用方模塊的內(nèi)部具體實現(xiàn)。一旦代碼里面涉及具體實現(xiàn)類,就違反了開閉原則。如果需要替換一種實現(xiàn),就需要修改代碼。
為了實現(xiàn)在模塊裝配的時候不用在程序里面動態(tài)指明,這就需要一種服務發(fā)現(xiàn)機制。Java SPI 就是提供了這樣一個機制:為某個接口尋找服務實現(xiàn)的機制。這有點類似 IOC 的思想,將裝配的控制權(quán)移交到了程序之外。
SPI 英文為 Service Provider Interface, 字面意思就是:“服務提供者的接口”,可以理解為專門提供給服務調(diào)用者或者擴展框架功能的開發(fā)者去使用的一個接口。
SPI 將服務接口和具體的服務實現(xiàn)分離開來,將服務調(diào)用方和服務實現(xiàn)者解耦,能夠提升程序的擴展性、可維護性。修改或者替換服務實現(xiàn)并不需要修改調(diào)用方。
使用場景
很多框架都使用了 Java 的 SPI 機制,比如:數(shù)據(jù)庫加載驅(qū)動,日志接口,以及 dubbo 的擴展實現(xiàn)等等。拿日志接口來說,Spring框架提供的日志服務 SLF4J 其實只是一個日志接口,但是 SLF4J 的具體實現(xiàn)可以有幾種,比如:Logback、Log4j、Log4j2 等等,而且還可以切換,在切換日志具體實現(xiàn)的時候我們是不需要更改項目代碼的,只需要在 Maven 依賴里面修改一些 pom 依賴就好了。
與API的區(qū)別
API:是指可以用來完成某項功能的類、接口或者方法。提供方提供實現(xiàn)方式,調(diào)用方只需調(diào)用即可。
SPI:是指用來繼承、擴展,完成自定義功能的類、接口或者方法。調(diào)用方可選擇使用提供方提供的內(nèi)置實現(xiàn),也可以自己實現(xiàn)。
SPI原理
Java SPI的具體約定為:當服務的提供者提供了服務接口后,在jar包的META-INF/services目錄下同時創(chuàng)建一個以服務接口全類名命名的文件,該文件的內(nèi)容就是實現(xiàn)該服務接口具體實現(xiàn)類的全名,當然這個服務接口實現(xiàn)類也必須在這個jar中。當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services下的配置文件找到具體的實現(xiàn)類,并裝載實例化,完成模塊的注入。Java就是通過掃描META-INF/services文件夾目錄下面的文件,把實現(xiàn)類加載到servciceLoader里面。
簡單來講,META-INF/services/下的配置文件名字就是接口的全類名,實現(xiàn)類的全類名就是問這個文件的內(nèi)容,如果有多個
實現(xiàn)類,可以全部寫在這個文件中,同時在實現(xiàn)類中要實現(xiàn)這個接口。
不定義在META-INF/services下面行不行?就想定義在別的地方可以嗎?
答案是不行。看下圖JDK源碼中,因為已經(jīng)定義為配置路徑,如果寫在別的地方,類加載器就會找不到了。

案例實現(xiàn)
代碼結(jié)構(gòu)如下:
├─main
│ ├─java
│ │ └─com
│ │ ├─test
│ │ │ └─Apple
│ │ │ └─Fruit
│ └─resources
│ ├─META-INF
│ │ └─services
│ │ └─com.test.Fruit
- 創(chuàng)建接口Fruit,模擬服務提供方接口
package com.test;
public interface Fruit {
}- 創(chuàng)建接口實現(xiàn)類Apple,實現(xiàn)Fruit接口;
package com.test;
public class Apple implements Fruit {
static {
System.out.println("hi,I am an apple!");
}
}- 在resources結(jié)構(gòu)下創(chuàng)建META-INF/services目錄,在這個目錄下,創(chuàng)建以這個接口名命名的文件com.test.Fruit,同時在這個文件中寫入這個實現(xiàn)類的全類名;
com.test.Apple
- 創(chuàng)建Test測試類,在測試類中創(chuàng)建一個類加載器ServiceLoader來實現(xiàn)本案例。
import com.test.Fruit;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<Fruit> test = ServiceLoader.load(Fruit.class);
for (Fruit item:test){
}
}
}執(zhí)行結(jié)果如下,表明本案例成功執(zhí)行。
hi,I am an apple!
以上就是Java的SPI機制以及基于SPI編程示例詳解的詳細內(nèi)容,更多關(guān)于Java SPI機制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實戰(zhàn)項目 醫(yī)院預約掛號系統(tǒng)
本文是一個Java語言編寫的實戰(zhàn)項目,是一個醫(yī)院預約掛號系統(tǒng),主要用到了jdbc+jsp+mysql+ajax等技術(shù),技術(shù)含量比較高,感興趣的童鞋跟著小編往下看吧2021-09-09
詳解Maven 搭建spring boot多模塊項目(附源碼)
這篇文章主要介紹了詳解Maven 搭建spring boot多模塊項目(附源碼),具有一定的參考價值,有興趣的可以了解一下2017-09-09
Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的思路案例詳解
本文通過兩個案例來介紹下Spring MVC 自定義數(shù)據(jù)轉(zhuǎn)換器的相關(guān)知識,每種方法通過實例圖文相結(jié)合給大家介紹的非常詳細,需要的朋友可以參考下2021-09-09
詳談Array和ArrayList的區(qū)別與聯(lián)系
下面小編就為大家?guī)硪黄斦凙rray和ArrayList的區(qū)別與聯(lián)系。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06
JAVA-4NIO之Channel之間的數(shù)據(jù)傳輸方法
下面小編就為大家?guī)硪黄狫AVA-4NIO之Channel之間的數(shù)據(jù)傳輸方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06

