本文分享自華為雲社群《Spring高手之路14——深入淺出:SPI機制在JDK與Spring Boot中的應用》,作者:磚業洋__ 。
SPI
(Service Provider Interface
) 是一種服務發現機制,它允許第三方提供者為核心庫或主框架提供實現或擴充套件。這種設計允許核心庫/框架在不修改自身程式碼的情況下,通過第三方實現來增強功能。
定義和發現:JDK
的SPI
主要通過在META-INF/services/
目錄下放置特定的檔案來指定哪些類實現了給定的服務介面。這些檔案的名稱應為介面的全限定名,內容為實現該介面的全限定類名。
載入機制:ServiceLoader
類使用Java
的類載入器機制從META-INF/services/
目錄下載入和範例化服務提供者。例如,ServiceLoader.load(MyServiceInterface.class)
會返回一個實現了MyServiceInterface
的範例迭代器。
缺點:JDK
原生的SPI
每次通過ServiceLoader
載入時都會初始化一個新的範例,沒有實現類的快取,也沒有考慮單例等高階功能。
更加靈活:Spring
的SPI
不僅僅是服務發現,它提供了一套完整的外掛機制。例如,可以為Spring
定義新的PropertySource
,ApplicationContextInitializer
等。
與IoC整合:與JDK
的SPI
不同,Spring
的SPI
與其IoC
(Inversion of Control
) 容器整合,使得在SPI
實現中可以利用Spring
的全部功能,如依賴注入。
條件匹配:Spring
提供了基於條件的匹配機制,這允許在某些條件下只載入特定的SPI
實現,例如,可以基於當前執行環境的不同來選擇載入哪個資料庫驅動。
設定:Spring
允許通過spring.factories
檔案在META-INF
目錄下進行設定,這與JDK
的SPI
很相似,但它提供了更多的功能和靈活性。
舉個類比的例子:
想象我們正在建造一個電視機,SPI
就像電視機上的一個USB
插口。這個插口可以插入各種裝置(例如U盤、遊戲手柄、電視棒等),但我們並不關心這些裝置的內部工作方式。這樣只需要提供一個標準的介面,其他公司(例如U盤製造商)可以為此介面提供實現。這樣,電視機可以在不更改自己內部程式碼的情況下使用各種新裝置,而裝置製造商也可以為各種電視機制造相容的裝置。
總之,SPI
是一種將介面定義與實現分離的設計模式,它鼓勵第三方為一個核心產品或框架提供外掛或實現,從而使核心產品能夠輕鬆地擴充套件功能。
在Java
的生態系統中,SPI
是一個核心概念,允許開發者提供擴充套件和替代的實現,而核心庫或應用不必更改,下面舉出一個例子來說明。
全部程式碼和步驟如下:
步驟1:定義一個服務介面,檔名: MessageService.java
package com.example.demo.service; public interface MessageService { String getMessage(); }
步驟2:為服務介面提供實現,這裡會提供兩個簡單的實現類。
HelloMessageService.java
package com.example.demo.service; public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello from HelloMessageService!"; } }
HiMessageService.java
package com.example.demo.service; public class HiMessageService implements MessageService { @Override public String getMessage() { return "Hi from HiMessageService!"; } }
這些實現就像不同品牌或型號的U盤或其他USB
裝置。每個裝置都有自己的功能和特性,但都遵循相同的USB
標準。
步驟3:註冊服務提供者
在資源目錄(通常是src/main/resources/
)下建立一個名為META-INF/services/
的資料夾。在這個資料夾中,建立一個名為com.example.demo.service.MessageService
的檔案(這是我們介面的全限定名),這個檔案沒有任何副檔名,所以不要加上.txt
這樣的字尾。檔案的內容應為我們的兩個實現類的全限定名,每個名字佔一行:
com.example.demo.service.HelloMessageService
com.example.demo.service.HiMessageService
META-INF/services/
是 Java SPI
(Service Provider Interface
) 機制中約定俗成的特定目錄。它不是隨意選擇的,而是 SPI
規範中明確定義的。因此,當使用 JDK
的 ServiceLoader
類來載入服務提供者時,它會特意去查詢這個路徑下的檔案。
請確保檔案的每一行只有一個名稱,並且沒有額外的空格或隱藏的字元,檔案使用UTF-8
編碼。
步驟4:使用ServiceLoader
載入和使用服務
package com.example.demo; import com.example.demo.service.MessageService; import java.util.ServiceLoader; public class DemoApplication { public static void main(String[] args) { ServiceLoader<MessageService> loaders = ServiceLoader.load(MessageService.class); for (MessageService service : loaders) { System.out.println(service.getMessage()); } } }
執行結果如下:
這說明ServiceLoader
成功地載入了我們為MessageService
介面提供的兩個實現,並且我們可以在不修改Main
類的程式碼的情況下,通過新增更多的實現類和更新META-INF/services/com.example.MessageService
檔案來擴充套件我們的服務。
想象一下買了一臺高階的智慧電視,這臺電視上有一個或多個HDMI
埠,這就是它與外部裝置連線的介面。
HDMI
埠的標準。在上面的程式碼中,MessageService
介面就是這個「HDMI
埠」,定義瞭如何與外部裝置交流。為服務介面提供實現:這類似於製造商為HDMI
介面生產各種裝置,如遊戲機、藍光播放器或串流媒體棒。在程式碼中,HelloMessageService
和HiMessageService
就是這些「HDMI
裝置」。每個裝置/實現都有其獨特的輸出,但都遵循了統一的HDMI
標準(MessageService
介面)。
註冊服務提供者:當我們購買了一個HDMI
裝置,它通常都會在包裝盒上明確標明「適用於HDMI
」。這就像一個標識,告訴使用者它可以連線到任何帶有HDMI
介面的電視。在SPI
的例子中,META-INF/services/
目錄和其中的檔案就像這個「標籤」,告訴JDK
哪些類是MessageService
的實現。
使用ServiceLoader載入和使用服務:當插入一個HDMI
裝置到電視上,並切換到正確的輸入頻道,電視就會顯示該裝置的內容。類似地,在程式碼的這個步驟中,ServiceLoader
就像電視的輸入選擇功能,能夠發現和使用所有已連線的HDMI
裝置(即MessageService
的所有實現)。
Spring
官方在其檔案和原始碼中多次提到了SPI
(Service Provider Interface
)的概念。但是,當我們說「Spring
的SPI
」時,通常指的是Spring
框架為開發者提供的一套可延伸的介面和抽象類,開發者可以基於這些介面和抽象類實現自己的版本。
在Spring
中,SPI
的概念與Spring Boot
使用的spring.factories
檔案的機制不完全一樣,但是它們都體現了可插拔、可延伸的思想。
Spring
的核心框架提供了很多介面和抽象類,如BeanPostProcessor
, PropertySource
, ApplicationContextInitializer
等,這些都可以看作是Spring
的SPI
。開發者可以實現這些介面來擴充套件Spring
的功能。這些介面允許開發者在Spring
容器的生命週期的不同階段介入,實現自己的邏輯。spring.factories
是Spring Boot
的一個特性,允許開發者自定義自動設定。通過spring.factories
檔案,開發者可以定義自己的自動設定類,這些類在Spring Boot
啟動時會被自動載入。
在這種情況下,SpringFactoriesLoader
的使用,尤其是通過spring.factories
檔案來載入和範例化定義的類,可以看作是一種特定的SPI
實現方式,但它特定於Spring Boot
。
在傳統的Spring
框架中,雖然沒有直接使用名為"SPI"
的術語,但其核心思想仍然存在。Spring
提供了多個擴充套件點,其中最具代表性的就是BeanPostProcessor
。在本節中,我們將通過一個簡單的MessageService
介面及其實現來探討如何利用Spring
的BeanPostProcessor
擴充套件點體現SPI
的思想。
提供兩個簡單的實現類。
HelloMessageService.java
package com.example.demo.service; public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello from HelloMessageService!"; } }
HiMessageService.java
package com.example.demo.service; public class HiMessageService implements MessageService { @Override public String getMessage() { return "Hi from HiMessageService!"; } }
定義BeanPostProcessor
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MessageServicePostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof MessageService) { return new MessageService() { @Override public String getMessage() { return ((MessageService) bean).getMessage() + " [Processed by Spring SPI]"; } }; } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
修改Spring
設定
將MessageServicePostProcessor
新增到Spring
設定中:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MessageServiceConfig { @Bean public MessageService helloMessageService() { return new HelloMessageService(); } @Bean public MessageService hiMessageService() { return new HiMessageService(); } @Bean public MessageServicePostProcessor messageServicePostProcessor() { return new MessageServicePostProcessor(); } }
執行程式
使用之前提供的DemoApplication
範例類:
package com.example.demo; import com.example.demo.configuration.MessageServiceConfig; import com.example.demo.service.MessageService; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class DemoApplication { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MessageServiceConfig.class); MessageService helloMessageService = context.getBean("helloMessageService", MessageService.class); MessageService hiMessageService = context.getBean("hiMessageService", MessageService.class); System.out.println(helloMessageService.getMessage()); System.out.println(hiMessageService.getMessage()); } }
執行結果:
現在,每一個MessageService
實現都被BeanPostProcessor
處理了,新增了額外的訊息「[Processed by Spring SPI]」
。這演示了Spring
的SPI
概念,通過BeanPostProcessor
來擴充套件或修改Spring
容器中的bean
。
有人可能留意到這裡紅色的警告,這個之前在講BeanPostProcessor
的時候也提到過,當BeanPostProcessor
自身被一個或多個BeanPostProcessor
處理時,就會出現這種情況。簡單地說,由於BeanPostProcessor
需要在其他bean
之前初始化,所以某些BeanPostProcessor
無法處理早期初始化的bean
,包括設定類和其他BeanPostProcessor
。解決辦法就是不要把MessageServicePostProcessor
放在設定類初始化,在設定類刪掉,再把MessageServicePostProcessor
加上@Component
註解。
類比文章開頭的電視機的例子:
Spring
應用程式,具體來說是DemoApplication
類。這個核心應用程式需要從某個服務(即MessageService
)獲取並列印一條訊息。MessageService
介面就是這個"USB
插口"。它為電視機提供了一個標準化的介面,即getMessage()
方法,但沒有規定具體怎麼實現。HelloMessageService
和HiMessageService
。它們為"USB
插口"(即MessageService
介面)提供了不同的裝置或實現。一個顯示「Hello from HelloMessageService!」
,另一個顯示「Hi from HiMessageService!」
。USB
裝置(即MessageService
的實現)並嘗試從中獲取訊息時,這個「魔法盒子」會介入,併為每條訊息新增「[Processed by Spring SPI]」
。Java
的設定方式,即MessageServiceConfig
類。這個「使用說明書」指導Spring
容器如何建立並管理MessageService
的範例,並且還指導它如何使用「魔法盒子」(即MessageServicePostProcessor
)來處理訊息。總的來說,與之前的例子相比,這個新範例提供了一個更加動態的場景,其中Spring
的BeanPostProcessor
擴充套件點允許我們攔截並修改bean
的行為,就像一個能夠干預並改變電視機顯示內容的智慧裝置。
Spring Boot
有一個與SPI
相似的機制,但它並不完全等同於Java
的標準SPI
。
Spring Boot
的自動設定機制主要依賴於spring.factories
檔案。這個檔案可以在多個jar
中存在,並且Spring Boot
會載入所有可見的spring.factories
檔案。我們可以在這個檔案中宣告一系列的自動設定類,這樣當滿足某些條件時,這些設定類會自動被Spring Boot
應用。
接下來會展示Spring SPI
思想的好例子,但是它與Spring Boot
緊密相關。
定義介面
package com.example.demo.service; public interface MessageService { String getMessage(); }
這裡會提供兩個簡單的實現類。
HelloMessageService.java
package com.example.demo.service; public class HelloMessageService implements MessageService { @Override public String getMessage() { return "Hello from HelloMessageService!"; } }
HiMessageService.java
package com.example.demo.service; public class HiMessageService implements MessageService { @Override public String getMessage() { return "Hi from HiMessageService!"; } }
註冊服務
在resources/META-INF
下建立一個檔名為spring.factories
。這個檔案裡,可以註冊MessageService
實現類。
com.example.demo.service.MessageService=com.example.demo.service.HelloMessageService,com.example.demo.service.HiMessageService
注意這裡com.example.demo.service.MessageService
是介面的全路徑,而com.example.demo.service.HelloMessageService,com.example.demo.service.HiMessageService
是實現類的全路徑。如果有多個實現類,它們應當用逗號分隔。
spring.factories
檔案中的條目鍵和值之間不能有換行,即key=value
形式的結構必須在同一行開始。但是,如果有多個值需要列出(如多個實現類),並且這些值是逗號分隔的,那麼可以使用反斜槓(\
)來換行。spring.factories
的名稱是約定俗成的。如果試圖使用一個不同的檔名,那麼 Spring Boot
的自動設定機制將不會識別它。
這裡spring.factories
又可以寫為
com.example.demo.service.MessageService=com.example.demo.service.HelloMessageService,\
com.example.demo.service.HiMessageService
直接在逗號後面回車IDEA
會自動補全反斜槓,保證鍵和值之間不能有換行即可。
使用SpringFactoriesLoader
來載入服務
package com.example.demo; import com.example.demo.service.MessageService; import org.springframework.core.io.support.SpringFactoriesLoader; import java.util.List; public class DemoApplication { public static void main(String[] args) { List<MessageService> services = SpringFactoriesLoader.loadFactories(MessageService.class, null); for (MessageService service : services) { System.out.println(service.getMessage()); } } }
SpringFactoriesLoader.loadFactories
的第二個引數是類載入器,此處我們使用預設的類載入器,所以傳遞null
。
執行結果:
這種方式利用了Spring
的SpringFactoriesLoader
,它允許開發者提供介面的多種實現,並通過spring.factories
檔案來註冊它們。這與JDK
的SPI
思想非常相似,只是在實現細節上有所不同。這也是Spring Boot
如何自動設定的基礎,它會查詢各種spring.factories
檔案,根據其中定義的類來初始化和設定bean
。
我們繼續使用電視機的例子來解釋:
Spring
應用,就像DemoApplication
。電視機是檢視不同訊號源或通道的裝置,我們的應用程式是為了執行並使用不同的服務實現。MessageService
介面。USB
插口是一個標準的介面,它允許連線各種裝置,就像MessageService
介面允許有多種實現方式。HelloMessageService
和HiMessageService
。每個USB
裝置在插入電視機後都有特定的內容或功能,這就像我們的每個服務實現返回不同的訊息。spring.factories
檔案。當我們將USB
裝置插入電視機時,電視機會檢查裝置的資訊或內容,spring.factories
檔案告訴Spring Boot
哪些服務實現是可用的,就像電視機知道有哪些USB
裝置被插入。SpringFactoriesLoader
。當我們要從電視機上檢視USB
內容時,電視機會掃描並顯示內容。同樣,當DemoApplication
執行時,SpringFactoriesLoader
會查詢並載入在spring.factories
檔案中列出的服務實現。簡化解釋:
當插入USB
裝置到電視機,期望電視機能夠識別並顯示該裝置的內容。
在我們的例子中,USB
裝置的內容就是從MessageService
實現類返回的訊息。
spring.factories
檔案就像電視機的內建目錄,告訴電視機哪些USB
裝置是已知的和可以使用的。
當我們的DemoApplication
(電視機)執行時,它使用SpringFactoriesLoader
(USB
掃描功能)來檢查哪些服務(USB
裝置)是可用的,並輸出相應的訊息(顯示USB
內容)。
總結:在這個Spring Boot
的SPI
例子中,我們展示了核心Spring
應用如何自動地識別和使用spring.factories
檔案中註冊的實現,這與電視機自動地識別和使用所有插入的USB
裝置有相似之處。
資料庫驅動的SPI
主要體現在JDBC
驅動的自動發現機制中。JDBC 4.0
引入了一個特性,允許驅動自動註冊到DriverManager
。這是通過使用Java
的SPI
來實現的。驅動jar
包內會有一個META-INF/services/java.sql.Driver
檔案,此檔案中包含了該驅動的Driver
實現類的全類名。這樣,當類路徑中有JDBC
驅動的jar
檔案時,Java
應用程式可以自動發現並載入JDBC
驅動,而無需明確地載入驅動類。
這意味著任何資料庫供應商都可以編寫其自己的JDBC
驅動程式,只要它遵循JDBC
驅動程式的SPI
,它就可以被任何使用JDBC
的Java
應用程式所使用。
當我們使用DriverManager.getConnection()
獲取資料庫連線時,背後正是利用SPI
機制載入合適的驅動程式。
以下是SPI
機制的具體工作方式:
在這裡,介面已經由Java
平臺定義,即java.sql.Driver
。
各巨量資料庫廠商(如Oracle
, MySQL
, PostgreSQL
等)為其資料庫提供了JDBC
驅動程式,它們都實現了java.sql.Driver
介面。例如,MySQL
的驅動程式中有一個類似於以下的類:
public class com.mysql.cj.jdbc.Driver implements java.sql.Driver { // 實現介面方法... }
直接上圖:
註冊服務提供者:對於MySQL
的驅動程式,可以在其JAR
檔案的META-INF/services
目錄下找到一個名為java.sql.Driver
的檔案,檔案內容如下:
com.mysql.cj.jdbc.Driver
直接上圖:
看到這裡是不是發現和第2
節舉的JDK SPI
的例子一樣?體會一下。
當我們呼叫DriverManager.getConnection(jdbcUrl, username, password)
時,DriverManager
會使用ServiceLoader
來查詢所有已註冊的java.sql.Driver
實現。然後,它會嘗試每一個驅動程式,直到找到一個可以處理給定jdbcUrl
的驅動程式。
以下是一個簡單的範例,展示如何使用JDBC SPI
獲取資料庫連線:
import java.sql.Connection; import java.sql.DriverManager; public class JdbcExample { public static void main(String[] args) { String jdbcUrl = "jdbc:mysql://localhost:3306/mydatabase"; String username = "root"; String password = "password"; try { Connection connection = DriverManager.getConnection(jdbcUrl, username, password); System.out.println("Connected to the database!"); connection.close(); } catch (Exception e) { e.printStackTrace(); } } }
在上述程式碼中,我們沒有明確指定使用哪個JDBC
驅動程式,因為DriverManager
會自動為我們選擇合適的驅動程式。
這種模組化和外掛化的機制使得我們可以輕鬆地為不同的資料庫切換驅動程式,只需要更改JDBC URL
並確保相應的驅動程式JAR
在類路徑上即可。
在Spring Boot
中,開發者通常不會直接與JDBC
的SPI
機制互動來獲取資料庫連線。Spring Boot
的自動設定機制隱藏了許多底層細節,使得設定和使用資料庫變得更加簡單。
一般會在application.properties
或application.yml
中設定資料庫連線資訊。
例如:
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=root spring.datasource.password=password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
在上述步驟中,Spring Boot
的自動設定機制會根據提供的依賴和設定資訊來初始化和設定DataSource
物件,這個物件管理資料庫連線。實際上,新增JDBC
驅動依賴時,Spring Boot
會使用JDK
的SPI
機制(在JDBC
規範中應用)來找到並載入相應的資料庫驅動。開發者雖然不直接與JDK
的SPI
互動,但在背後Spring Boot
確實利用了JDK SPI
機制來獲取資料庫連線。
這種機制有點類似於Java
的SPI
,因為它允許第三方庫提供一些預設的設定。但它比Java
的SPI
更為強大和靈活,因為Spring Boot
提供了大量的註解(如@ConditionalOnClass
、@ConditionalOnProperty
、@ConditionalOnMissingBean
等)來控制自動設定類是否應該被載入和應用。
總的來說,Spring Boot
的spring.factories
機制和Java
的SPI
在概念上是相似的,但它們在實現細節和用途上有所不同。
讓我們建立一個簡化的實際例子,假設我們要為不同的訊息服務(如SMS
和Email
)建立自動設定。
MessageService介面:
package com.example.demo.service; public interface MessageService { void send(String message); }
SMS服務實現:
package com.example.demo.service.impl; import com.example.demo.service.MessageService; public class SmsService implements MessageService { @Override public void send(String message) { System.out.println("Sending SMS: " + message); } }
Email服務實現:
package com.example.demo.service.impl; import com.example.demo.service.MessageService; public class EmailService implements MessageService { @Override public void send(String message) { System.out.println("Sending Email: " + message); } }
自動設定類:
package com.example.demo.configuration; import com.example.demo.service.EmailService; import com.example.demo.service.MessageService; import com.example.demo.service.SmsService; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MessageAutoConfiguration { @Bean @ConditionalOnProperty(name = "message.type", havingValue = "sms") public MessageService smsService() { return new SmsService(); } @Bean @ConditionalOnProperty(name = "message.type", havingValue = "email") public MessageService emailService() { return new EmailService(); } }
這個類提供兩個條件性的beans
(元件),分別是SmsService
和EmailService
。這些beans
的建立取決於application.properties
檔案中特定的屬性值。
當application.properties
或application.yml
中定義的屬性message.type
的值為sms
時,此條件為true
。此時,smsService()
方法將被呼叫,從而建立一個SmsService
的bean
。
當application.properties
或application.yml
中定義的屬性message.type
的值為email
時,此條件為true
。此時,emailService()
方法將被呼叫,從而建立一個EmailService
的bean
。
spring.factories檔案:
在src/main/resources/META-INF
目錄下建立一個spring.factories
檔案,內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.configuration.MessageAutoConfiguration
application.properties檔案:
message.type=sms
MessageTester元件:
package com.example.demo; import com.example.demo.service.MessageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class MessageTester { @Autowired private MessageService messageService; @PostConstruct public void init() { messageService.send("Hello World"); } }
DemoApplication主程式:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
執行結果:
在上述例子中,我們建立了一個MessageService
介面和兩個實現(SmsService
和EmailService
)。然後,我們建立了一個自動設定類,其中包含兩個bean
定義,這兩個bean
定義分別基於application.properties
中的屬性值條件性地建立。在spring.factories
檔案中,我們宣告了這個自動設定類,以便Spring Boot
在啟動時能夠自動載入它。
在此,繼續用電視機的例子昇華理解下
電視機類比
總體概念:假設電視機(TV
)是一個Java
應用。
電視機的各種插槽,如HDMI
、USB
、VGA
等,可以視為應用中的SPI
介面。
插入這些插槽的裝置(如DVD
播放器、遊戲機、USB
驅動器等)可以視為SPI
的實現。
當我們購買電視機時,不知道將會連線哪種裝置,可能是DVD
播放器,也可能是遊戲機。
但是,只要這些裝置遵循了插槽的標準(例如,HDMI
標準),就可以將其插入電視機並使其工作。
這就像Java
的SPI
機制:為了能讓多個供應商提供實現,Java
定義了一個介面,供應商提供具體的實現。
現在,想象一下現代的智慧電視。當插入一個裝置,電視機不僅可以識別它,還可能根據所連線的裝置型別自動調整設定,例如選擇正確的輸入源、優化影象質量等。
這就像Spring Boot
的自動設定:當Spring Boot
應用啟動時,它會檢查classpath
上的庫,並根據存在的庫自動設定應用。
電視機的自動設定可以類比為Spring Boot
中的spring.factories
和各種@Conditional
…註解。它們決定在什麼條件下進行哪種設定。
如果電視製造商想為新型的插槽或連線技術開發電視,它可以很容易地在其電視機型中新增新的插槽。
同樣地,使用Spring Boot
,如果要為應用新增新功能或庫,只需新增相關的依賴,然後Spring Boot
會自動識別並設定這些新功能。
通過這種類比,電視機的插槽和自動設定功能為我們提供了一個直觀的方式來理解Java
的SPI
機制和Spring Boot
的自動設定如何工作,以及它們如何為應用開發者提供便利。
SPI
,即服務提供者介面,是一種特定的設計模式。它允許框架或核心庫為第三方開發者提供一個預定義的介面,從而使他們能夠為框架提供自定義的實現或擴充套件。
核心目標:
SPI
機制讓框架的核心與其擴充套件部分保持解耦,使核心程式碼不依賴於具體的實現。Java
的ServiceLoader
)動態地發現和載入所需的實現。價值:
為框架或庫的使用者提供更多的自定義選項和靈活性。
允許框架的核心部分保持穩定,同時能夠容納新的功能和擴充套件。
SPI與「開閉原則」:
「開閉原則」提倡軟體實體應該對擴充套件開放,但對修改封閉。即在不改變現有程式碼的前提下,通過擴充套件來增加新的功能。
SPI如何體現「開閉原則」:
SPI
提供了一種標準化的方式,使第三方開發者可以為現有系統提供新的實現或功能。SPI
實現可以獨立地進化和發展,互不影響。總之,SPI
是一種使軟體框架或庫更加模組化、可延伸和可維護的有效方法。通過遵循「開閉原則」,SPI
確保了系統的穩定性和靈活性,從而滿足了不斷變化的業務需求。