欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入淺析SPI機(jī)制在JDK與Spring?Boot中的應(yīng)用

 更新時間:2023年09月07日 15:04:51   作者:磚業(yè)洋__  
SPI是一種使軟件框架或庫更加模塊化、可擴(kuò)展和可維護(hù)的有效方法。通過遵循“開閉原則”,?SPI?確保了系統(tǒng)的穩(wěn)定性和靈活性,從而滿足了不斷變化的業(yè)務(wù)需求,這篇文章主要介紹了SPI機(jī)制在JDK與Spring?Boot中的應(yīng)用,需要的朋友可以參考下

1. SPI解讀:什么是SPI?

   SPI ( Service Provider Interface ) 是一種服務(wù)發(fā)現(xiàn)機(jī)制,它允許第三方提供者為核心庫或主框架提供實(shí)現(xiàn)或擴(kuò)展。這種設(shè)計允許核心庫/框架在不修改自身代碼的情況下,通過第三方實(shí)現(xiàn)來增強(qiáng)功能。

1.JDK原生的SPI

  • 定義和發(fā)現(xiàn)JDK SPI 主要通過在 META-INF/services/ 目錄下放置特定的文件來指定哪些類實(shí)現(xiàn)了給定的服務(wù)接口。這些文件的名稱應(yīng)為接口的全限定名,內(nèi)容為實(shí)現(xiàn)該接口的全限定類名。
  • 加載機(jī)制ServiceLoader 類使用 Java 的類加載器機(jī)制從 META-INF/services/ 目錄下加載和實(shí)例化服務(wù)提供者。例如, ServiceLoader.load(MyServiceInterface.class) 會返回一個實(shí)現(xiàn)了 MyServiceInterface 的實(shí)例迭代器。
  • 缺點(diǎn)JDK 原生的 SPI 每次通過 ServiceLoader 加載時都會初始化一個新的實(shí)例,沒有實(shí)現(xiàn)類的緩存,也沒有考慮單例等高級功能。

2.Spring的SPI

  • 更加靈活Spring SPI 不僅僅是服務(wù)發(fā)現(xiàn),它提供了一套完整的插件機(jī)制。例如,可以為 Spring 定義新的 PropertySource , ApplicationContextInitializer 等。
  • 與IoC集成:與 JDK SPI 不同, Spring SPI 與其 IoC ( Inversion of Control ) 容器集成,使得在 SPI 實(shí)現(xiàn)中可以利用 Spring 的全部功能,如依賴注入。
  • 條件匹配Spring 提供了基于條件的匹配機(jī)制,這允許在某些條件下只加載特定的 SPI 實(shí)現(xiàn),例如,可以基于當(dāng)前運(yùn)行環(huán)境的不同來選擇加載哪個數(shù)據(jù)庫驅(qū)動。
  • 配置Spring 允許通過 spring.factories 文件在 META-INF 目錄下進(jìn)行配置,這與 JDK SPI 很相似,但它提供了更多的功能和靈活性。

舉個類比的例子:

  想象我們正在建造一個電視機(jī), SPI 就像電視機(jī)上的一個 USB 插口。這個插口可以插入各種設(shè)備(例如U盤、游戲手柄、電視棒等),但我們并不關(guān)心這些設(shè)備的內(nèi)部工作方式。這樣只需要提供一個標(biāo)準(zhǔn)的接口,其他公司(例如U盤制造商)可以為此接口提供實(shí)現(xiàn)。這樣,電視機(jī)可以在不更改自己內(nèi)部代碼的情況下使用各種新設(shè)備,而設(shè)備制造商也可以為各種電視機(jī)制造兼容的設(shè)備。

  總之, SPI 是一種將接口定義與實(shí)現(xiàn)分離的設(shè)計模式,它鼓勵第三方為一個核心產(chǎn)品或框架提供插件或?qū)崿F(xiàn),從而使核心產(chǎn)品能夠輕松地擴(kuò)展功能。

2. SPI在JDK中的應(yīng)用示例

  在 Java 的生態(tài)系統(tǒng)中, SPI 是一個核心概念,允許開發(fā)者提供擴(kuò)展和替代的實(shí)現(xiàn),而核心庫或應(yīng)用不必更改,下面舉出一個例子來說明。

全部代碼和步驟如下:

步驟1:定義一個服務(wù)接口,文件名: MessageService.java

package com.example.demo.service;
public interface MessageService {
String getMessage();
}

步驟2:為服務(wù)接口提供實(shí)現(xiàn),這里會提供兩個簡單的實(shí)現(xiàn)類。

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!";
}
}

這些實(shí)現(xiàn)就像不同品牌或型號的U盤或其他 USB 設(shè)備。每個設(shè)備都有自己的功能和特性,但都遵循相同的 USB 標(biāo)準(zhǔn)。

步驟3:注冊服務(wù)提供者

  在資源目錄(通常是 src/main/resources/ )下創(chuàng)建一個名為 META-INF/services/ 的文件夾。在這個文件夾中,創(chuàng)建一個名為 com.example.demo.service.MessageService 的文件(這是我們接口的全限定名),這個文件沒有任何文件擴(kuò)展名,所以不要加上 .txt 這樣的后綴。文件的內(nèi)容應(yīng)為我們的兩個實(shí)現(xiàn)類的全限定名,每個名字占一行:

com.example.demo.service.HelloMessageService
com.example.demo.service.HiMessageService

   META-INF/services/ Java SPI ( Service Provider Interface ) 機(jī)制中約定俗成的特定目錄。它不是隨意選擇的,而是 SPI 規(guī)范中明確定義的。因此,當(dāng)使用 JDK ServiceLoader 類來加載服務(wù)提供者時,它會特意去查找這個路徑下的文件。

  請確保文件的每一行只有一個名稱,并且沒有額外的空格或隱藏的字符,文件使用 UTF-8 編碼。

步驟4:使用 ServiceLoader 加載和使用服務(wù)

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

運(yùn)行結(jié)果如下:

  這說明 ServiceLoader 成功地加載了我們?yōu)?MessageService 接口提供的兩個實(shí)現(xiàn),并且我們可以在不修改 Main 類的代碼的情況下,通過添加更多的實(shí)現(xiàn)類和更新 META-INF/services/com.example.MessageService 文件來擴(kuò)展我們的服務(wù)。

想象一下買了一臺高端的智能電視,這臺電視上有一個或多個 HDMI 端口,這就是它與外部設(shè)備連接的接口。

  • 定義服務(wù)接口:這就像電視定義了 HDMI 端口的標(biāo)準(zhǔn)。在上面的代碼中, MessageService 接口就是這個“ HDMI 端口”,定義了如何與外部設(shè)備交流。
  • 為服務(wù)接口提供實(shí)現(xiàn):這類似于制造商為 HDMI 接口生產(chǎn)各種設(shè)備,如游戲機(jī)、藍(lán)光播放器或流媒體棒。在代碼中, HelloMessageService HiMessageService 就是這些“ HDMI 設(shè)備”。每個設(shè)備/實(shí)現(xiàn)都有其獨(dú)特的輸出,但都遵循了統(tǒng)一的 HDMI 標(biāo)準(zhǔn)( MessageService 接口)。
  • 注冊服務(wù)提供者:當(dāng)我們購買了一個 HDMI 設(shè)備,它通常都會在包裝盒上明確標(biāo)明“適用于 HDMI ”。這就像一個標(biāo)識,告訴用戶它可以連接到任何帶有 HDMI 接口的電視。在 SPI 的例子中, META-INF/services/ 目錄和其中的文件就像這個“標(biāo)簽”,告訴 JDK 哪些類是 MessageService 的實(shí)現(xiàn)。
  • 使用ServiceLoader加載和使用服務(wù):當(dāng)插入一個 HDMI 設(shè)備到電視上,并切換到正確的輸入頻道,電視就會顯示該設(shè)備的內(nèi)容。類似地,在代碼的這個步驟中, ServiceLoader 就像電視的輸入選擇功能,能夠發(fā)現(xiàn)和使用所有已連接的 HDMI 設(shè)備(即 MessageService 的所有實(shí)現(xiàn))。

3. SPI在Spring框架中的應(yīng)用

   Spring 官方在其文檔和源代碼中多次提到了 SPI Service Provider Interface )的概念。但是,當(dāng)我們說“ Spring SPI ”時,通常指的是 Spring 框架為開發(fā)者提供的一套可擴(kuò)展的接口和抽象類,開發(fā)者可以基于這些接口和抽象類實(shí)現(xiàn)自己的版本。

Spring 中, SPI 的概念與 Spring Boot 使用的 spring.factories 文件的機(jī)制不完全一樣,但是它們都體現(xiàn)了可插拔、可擴(kuò)展的思想。

1.Spring的SPI

  • Spring 的核心框架提供了很多接口和抽象類,如 BeanPostProcessor , PropertySource , ApplicationContextInitializer 等,這些都可以看作是 Spring SPI 。開發(fā)者可以實(shí)現(xiàn)這些接口來擴(kuò)展 Spring 的功能。這些接口允許開發(fā)者在 Spring 容器的生命周期的不同階段介入,實(shí)現(xiàn)自己的邏輯。 Spring

2.Boot的spring.factories機(jī)制

  • spring.factories Spring Boot 的一個特性,允許開發(fā)者自定義自動配置。通過 spring.factories 文件,開發(fā)者可以定義自己的自動配置類,這些類在 Spring Boot 啟動時會被自動加載。
  • 在這種情況下, SpringFactoriesLoader 的使用,尤其是通過 spring.factories 文件來加載和實(shí)例化定義的類,可以看作是一種特定的 SPI 實(shí)現(xiàn)方式,但它特定于 Spring Boot

3.1 傳統(tǒng)Spring框架中的SPI思想

  在傳統(tǒng)的 Spring 框架中,雖然沒有直接使用名為 "SPI" 的術(shù)語,但其核心思想仍然存在。 Spring 提供了多個擴(kuò)展點(diǎn),其中最具代表性的就是 BeanPostProcessor 。在本節(jié)中,我們將通過一個簡單的 MessageService 接口及其實(shí)現(xiàn)來探討如何利用 Spring BeanPostProcessor 擴(kuò)展點(diǎn)體現(xiàn) SPI 的思想。

提供兩個簡單的實(shí)現(xiàn)類。

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

執(zhí)行程序

使用之前提供的 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());
}
}

運(yùn)行結(jié)果:

  現(xiàn)在,每一個 MessageService 實(shí)現(xiàn)都被 BeanPostProcessor 處理了,添加了額外的消息 “[Processed by Spring SPI]” 。這演示了 Spring SPI 概念,通過 BeanPostProcessor 來擴(kuò)展或修改 Spring 容器中的 bean 。

  有人可能留意到這里紅色的警告,這個之前在講 BeanPostProcessor 的時候也提到過,當(dāng) BeanPostProcessor 自身被一個或多個 BeanPostProcessor 處理時,就會出現(xiàn)這種情況。簡單地說,由于 BeanPostProcessor 需要在其他 bean 之前初始化,所以某些 BeanPostProcessor 無法處理早期初始化的 bean ,包括配置類和其他 BeanPostProcessor 。解決辦法就是不要把 MessageServicePostProcessor 放在配置類初始化,在配置類刪掉,再把 MessageServicePostProcessor 加上 @Component 注解。

類比文章開頭的電視機(jī)的例子:

  • 電視機(jī)與USB插口: 在這個新的示例中,電視機(jī)仍然是核心的 Spring 應(yīng)用程序,具體來說是 DemoApplication 類。這個核心應(yīng)用程序需要從某個服務(wù)(即 MessageService )獲取并打印一條消息。
  • USB插口: 與之前一樣, MessageService 接口就是這個" USB 插口"。它為電視機(jī)提供了一個標(biāo)準(zhǔn)化的接口,即 getMessage() 方法,但沒有規(guī)定具體怎么實(shí)現(xiàn)。
  • 設(shè)備制造商與他們的產(chǎn)品: 在這里,我們有兩種設(shè)備制造商或第三方提供者: HelloMessageService HiMessageService 。它們?yōu)?quot; USB 插口"(即 MessageService 接口)提供了不同的設(shè)備或?qū)崿F(xiàn)。一個顯示 “Hello from HelloMessageService!” ,另一個顯示 “Hi from HiMessageService!”
  • BeanPostProcessor: 這是一個特殊的“魔法盒子”,可以將其視為一個能夠攔截并修改電視機(jī)顯示內(nèi)容的智能設(shè)備。當(dāng)插入 USB 設(shè)備(即 MessageService 的實(shí)現(xiàn))并嘗試從中獲取消息時,這個“魔法盒子”會介入,并為每條消息添加 “[Processed by Spring SPI]” 。
  • Spring上下文配置: 這依然是電視機(jī)的使用說明書,但現(xiàn)在是使用了基于 Java 的配置方式,即 MessageServiceConfig 類。這個“使用說明書”指導(dǎo) Spring 容器如何創(chuàng)建并管理 MessageService 的實(shí)例,并且還指導(dǎo)它如何使用“魔法盒子”(即 MessageServicePostProcessor )來處理消息。

  總的來說,與之前的例子相比,這個新示例提供了一個更加動態(tài)的場景,其中 Spring BeanPostProcessor 擴(kuò)展點(diǎn)允許我們攔截并修改 bean 的行為,就像一個能夠干預(yù)并改變電視機(jī)顯示內(nèi)容的智能設(shè)備。

3.2 Spring Boot中的SPI思想

   Spring Boot 有一個與 SPI 相似的機(jī)制,但它并不完全等同于 Java 的標(biāo)準(zhǔn) SPI 。

   Spring Boot 的自動配置機(jī)制主要依賴于 spring.factories 文件。這個文件可以在多個 jar 中存在,并且 Spring Boot 會加載所有可見的 spring.factories 文件。我們可以在這個文件中聲明一系列的自動配置類,這樣當(dāng)滿足某些條件時,這些配置類會自動被 Spring Boot 應(yīng)用。

接下來會展示 Spring SPI 思想的好例子,但是它與 Spring Boot 緊密相關(guān)。

定義接口

package com.example.demo.service;
public interface MessageService {
String getMessage();
}

這里會提供兩個簡單的實(shí)現(xiàn)類。

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!";
}
}

注冊服務(wù)

resources/META-INF 下創(chuàng)建一個文件名為 spring.factories 。這個文件里,可以注冊 MessageService 實(shí)現(xiàn)類。

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 是實(shí)現(xiàn)類的全路徑。如果有多個實(shí)現(xiàn)類,它們應(yīng)當(dāng)用逗號分隔。

   spring.factories 文件中的條目鍵和值之間不能有換行,即 key=value 形式的結(jié)構(gòu)必須在同一行開始。但是,如果有多個值需要列出(如多個實(shí)現(xiàn)類),并且這些值是逗號分隔的,那么可以使用反斜杠( \ )來換行。 spring.factories 的名稱是約定俗成的。如果試圖使用一個不同的文件名,那么 Spring Boot 的自動配置機(jī)制將不會識別它。

這里 spring.factories 又可以寫為

com.example.demo.service.MessageService=com.example.demo.service.HelloMessageService,\
com.example.demo.service.HiMessageService

直接在逗號后面回車 IDEA 會自動補(bǔ)全反斜杠,保證鍵和值之間不能有換行即可。

使用 SpringFactoriesLoader 來加載服務(wù)

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 的第二個參數(shù)是類加載器,此處我們使用默認(rèn)的類加載器,所以傳遞 null 。

運(yùn)行結(jié)果:

  這種方式利用了 Spring SpringFactoriesLoader ,它允許開發(fā)者提供接口的多種實(shí)現(xiàn),并通過 spring.factories 文件來注冊它們。這與 JDK SPI 思想非常相似,只是在實(shí)現(xiàn)細(xì)節(jié)上有所不同。這也是 Spring Boot 如何自動配置的基礎(chǔ),它會查找各種 spring.factories 文件,根據(jù)其中定義的類來初始化和配置 bean 。

我們繼續(xù)使用電視機(jī)的例子來解釋:

  • 電視機(jī): 這是我們的 Spring 應(yīng)用,就像 DemoApplication 。電視機(jī)是查看不同信號源或通道的設(shè)備,我們的應(yīng)用程序是為了運(yùn)行并使用不同的服務(wù)實(shí)現(xiàn)。
  • USB插口: 這代表我們的 MessageService 接口。 USB 插口是一個標(biāo)準(zhǔn)的接口,它允許連接各種設(shè)備,就像 MessageService 接口允許有多種實(shí)現(xiàn)方式。
  • USB設(shè)備(如U盤或移動硬盤): 這代表我們的服務(wù)實(shí)現(xiàn),例如 HelloMessageService HiMessageService 。每個 USB 設(shè)備在插入電視機(jī)后都有特定的內(nèi)容或功能,這就像我們的每個服務(wù)實(shí)現(xiàn)返回不同的消息。
  • 電視機(jī)的USB設(shè)備目錄: 這是 spring.factories 文件。當(dāng)我們將 USB 設(shè)備插入電視機(jī)時,電視機(jī)會檢查設(shè)備的信息或內(nèi)容, spring.factories 文件告訴 Spring Boot 哪些服務(wù)實(shí)現(xiàn)是可用的,就像電視機(jī)知道有哪些 USB 設(shè)備被插入。
  • 電視機(jī)的USB掃描功能: 這就是 SpringFactoriesLoader 。當(dāng)我們要從電視機(jī)上查看 USB 內(nèi)容時,電視機(jī)會掃描并顯示內(nèi)容。同樣,當(dāng) DemoApplication 運(yùn)行時, SpringFactoriesLoader 會查找并加載在 spring.factories 文件中列出的服務(wù)實(shí)現(xiàn)。

簡化解釋:

  • 當(dāng)插入 USB 設(shè)備到電視機(jī),期望電視機(jī)能夠識別并顯示該設(shè)備的內(nèi)容。
  • 在我們的例子中, USB 設(shè)備的內(nèi)容就是從 MessageService 實(shí)現(xiàn)類返回的消息。
  • spring.factories 文件就像電視機(jī)的內(nèi)置目錄,告訴電視機(jī)哪些 USB 設(shè)備是已知的和可以使用的。
  • 當(dāng)我們的 DemoApplication (電視機(jī))運(yùn)行時,它使用 SpringFactoriesLoader USB 掃描功能)來檢查哪些服務(wù)( USB 設(shè)備)是可用的,并輸出相應(yīng)的消息(顯示 USB 內(nèi)容)。

  總結(jié):在這個 Spring Boot SPI 例子中,我們展示了核心 Spring 應(yīng)用如何自動地識別和使用 spring.factories 文件中注冊的實(shí)現(xiàn),這與電視機(jī)自動地識別和使用所有插入的 USB 設(shè)備有相似之處。

4. SPI在JDBC驅(qū)動加載中的應(yīng)用

  數(shù)據(jù)庫驅(qū)動的 SPI 主要體現(xiàn)在 JDBC 驅(qū)動的自動發(fā)現(xiàn)機(jī)制中。 JDBC 4.0 引入了一個特性,允許驅(qū)動自動注冊到 DriverManager 。這是通過使用 Java SPI 來實(shí)現(xiàn)的。驅(qū)動 jar 包內(nèi)會有一個 META-INF/services/java.sql.Driver 文件,此文件中包含了該驅(qū)動的 Driver 實(shí)現(xiàn)類的全類名。這樣,當(dāng)類路徑中有 JDBC 驅(qū)動的 jar 文件時, Java 應(yīng)用程序可以自動發(fā)現(xiàn)并加載 JDBC 驅(qū)動,而無需明確地加載驅(qū)動類。

  這意味著任何數(shù)據(jù)庫供應(yīng)商都可以編寫其自己的 JDBC 驅(qū)動程序,只要它遵循 JDBC 驅(qū)動程序的 SPI ,它就可以被任何使用 JDBC Java 應(yīng)用程序所使用。

當(dāng)我們使用 DriverManager.getConnection() 獲取數(shù)據(jù)庫連接時,背后正是利用 SPI 機(jī)制加載合適的驅(qū)動程序。

以下是 SPI 機(jī)制的具體工作方式:

1.定義服務(wù)接口

在這里,接口已經(jīng)由 Java 平臺定義,即 java.sql.Driver 。

2.為接口提供實(shí)現(xiàn)

各大數(shù)據(jù)庫廠商(如 Oracle , MySQL , PostgreSQL 等)為其數(shù)據(jù)庫提供了 JDBC 驅(qū)動程序,它們都實(shí)現(xiàn)了 java.sql.Driver 接口。例如, MySQL 的驅(qū)動程序中有一個類似于以下的類:

public class com.mysql.cj.jdbc.Driver implements java.sql.Driver {
// 實(shí)現(xiàn)接口方法...
}

直接上圖:

3.注冊服務(wù)提供者

對于 MySQL 的驅(qū)動程序,可以在其 JAR 文件的 META-INF/services 目錄下找到一個名為 java.sql.Driver 的文件,文件內(nèi)容如下:

com.mysql.cj.jdbc.Driver

直接上圖:

看到這里是不是發(fā)現(xiàn)和第 2 節(jié)舉的 JDK SPI 的例子一樣?體會一下。

4.使用SPI來加載和使用服務(wù)

  當(dāng)我們調(diào)用 DriverManager.getConnection(jdbcUrl, username, password) 時, DriverManager 會使用 ServiceLoader 來查找所有已注冊的 java.sql.Driver 實(shí)現(xiàn)。然后,它會嘗試每一個驅(qū)動程序,直到找到一個可以處理給定 jdbcUrl 的驅(qū)動程序。

以下是一個簡單的示例,展示如何使用 JDBC SPI 獲取數(shù)據(jù)庫連接:

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 驅(qū)動程序,因?yàn)?DriverManager 會自動為我們選擇合適的驅(qū)動程序。

  這種模塊化和插件化的機(jī)制使得我們可以輕松地為不同的數(shù)據(jù)庫切換驅(qū)動程序,只需要更改 JDBC URL 并確保相應(yīng)的驅(qū)動程序 JAR 在類路徑上即可。

  在 Spring Boot 中,開發(fā)者通常不會直接與 JDBC SPI 機(jī)制交互來獲取數(shù)據(jù)庫連接。 Spring Boot 的自動配置機(jī)制隱藏了許多底層細(xì)節(jié),使得配置和使用數(shù)據(jù)庫變得更加簡單。

一般會在 application.properties application.yml 中配置數(shù)據(jù)庫連接信息。

例如:

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 的自動配置機(jī)制會根據(jù)提供的依賴和配置信息來初始化和配置 DataSource 對象,這個對象管理數(shù)據(jù)庫連接。實(shí)際上,添加 JDBC 驅(qū)動依賴時, Spring Boot 會使用 JDK SPI 機(jī)制(在 JDBC 規(guī)范中應(yīng)用)來找到并加載相應(yīng)的數(shù)據(jù)庫驅(qū)動。開發(fā)者雖然不直接與 JDK SPI 交互,但在背后 Spring Boot 確實(shí)利用了 JDK SPI 機(jī)制來獲取數(shù)據(jù)庫連接。

5. 如何通過Spring Boot自動配置理解SPI思想

  這種機(jī)制有點(diǎn)類似于 Java SPI ,因?yàn)樗试S第三方庫提供一些默認(rèn)的配置。但它比 Java SPI 更為強(qiáng)大和靈活,因?yàn)?Spring Boot 提供了大量的注解(如 @ConditionalOnClass 、 @ConditionalOnProperty @ConditionalOnMissingBean 等)來控制自動配置類是否應(yīng)該被加載和應(yīng)用。

  總的來說, Spring Boot spring.factories 機(jī)制和 Java SPI 在概念上是相似的,但它們在實(shí)現(xiàn)細(xì)節(jié)和用途上有所不同。

讓我們創(chuàng)建一個簡化的實(shí)際例子,假設(shè)我們要為不同的消息服務(wù)(如 SMS Email )創(chuàng)建自動配置。

MessageService接口

package com.example.demo.service;
public interface MessageService {
void send(String message);
}

SMS服務(wù)實(shí)現(xiàn)

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服務(wù)實(shí)現(xiàn)

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 的創(chuàng)建取決于 application.properties 文件中特定的屬性值。

  • @ConditionalOnProperty(name = “message.type”, havingValue = “sms”)

  當(dāng) application.properties application.yml 中定義的屬性 message.type 的值為 sms 時,此條件為 true 。此時, smsService() 方法將被調(diào)用,從而創(chuàng)建一個 SmsService bean

  • @ConditionalOnProperty(name = “message.type”, havingValue = “email”)

  當(dāng) application.properties application.yml 中定義的屬性 message.type 的值為 email 時,此條件為 true 。此時, emailService() 方法將被調(diào)用,從而創(chuàng)建一個 EmailService bean

spring.factories文件

src/main/resources/META-INF 目錄下創(chuàng)建一個 spring.factories 文件,內(nèi)容如下:

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

運(yùn)行結(jié)果:

  在上述例子中,我們創(chuàng)建了一個 MessageService 接口和兩個實(shí)現(xiàn)( SmsService EmailService )。然后,我們創(chuàng)建了一個自動配置類,其中包含兩個 bean 定義,這兩個 bean 定義分別基于 application.properties 中的屬性值條件性地創(chuàng)建。在 spring.factories 文件中,我們聲明了這個自動配置類,以便 Spring Boot 在啟動時能夠自動加載它。

在此,繼續(xù)用電視機(jī)的例子升華理解下

電視機(jī)類比

1.總體概念

  • 假設(shè)電視機(jī)( TV )是一個 Java 應(yīng)用。
  • 電視機(jī)的各種插槽,如 HDMI USB VGA 等,可以視為應(yīng)用中的 SPI 接口。
  • 插入這些插槽的設(shè)備(如 DVD 播放器、游戲機(jī)、 USB 驅(qū)動器等)可以視為 SPI 的實(shí)現(xiàn)。

2.Java的SPI

  • 當(dāng)我們購買電視機(jī)時,不知道將會連接哪種設(shè)備,可能是 DVD 播放器,也可能是游戲機(jī)。
  • 但是,只要這些設(shè)備遵循了插槽的標(biāo)準(zhǔn)(例如, HDMI 標(biāo)準(zhǔn)),就可以將其插入電視機(jī)并使其工作。
  • 這就像 Java SPI 機(jī)制:為了能讓多個供應(yīng)商提供實(shí)現(xiàn), Java 定義了一個接口,供應(yīng)商提供具體的實(shí)現(xiàn)。

3.Spring Boot的自動配置

  • 現(xiàn)在,想象一下現(xiàn)代的智能電視。當(dāng)插入一個設(shè)備,電視機(jī)不僅可以識別它,還可能根據(jù)所連接的設(shè)備類型自動調(diào)整設(shè)置,例如選擇正確的輸入源、優(yōu)化圖像質(zhì)量等。
  • 這就像 Spring Boot 的自動配置:當(dāng) Spring Boot 應(yīng)用啟動時,它會檢查 classpath 上的庫,并根據(jù)存在的庫自動配置應(yīng)用。
  • 電視機(jī)的自動設(shè)置可以類比為 Spring Boot 中的 spring.factories 和各種 @Conditional …注解。它們決定在什么條件下進(jìn)行哪種配置。

4.擴(kuò)展性

  • 如果電視制造商想為新型的插槽或連接技術(shù)開發(fā)電視,它可以很容易地在其電視機(jī)型中添加新的插槽。
  • 同樣地,使用 Spring Boot ,如果要為應(yīng)用添加新功能或庫,只需添加相關(guān)的依賴,然后 Spring Boot 會自動識別并配置這些新功能。

  通過這種類比,電視機(jī)的插槽和自動設(shè)置功能為我們提供了一個直觀的方式來理解 Java SPI 機(jī)制和 Spring Boot 的自動配置如何工作,以及它們?nèi)绾螢閼?yīng)用開發(fā)者提供便利。

6. SPI(Service Provider Interface)總結(jié)

   SPI ,即服務(wù)提供者接口,是一種特定的設(shè)計模式。它允許框架或核心庫為第三方開發(fā)者提供一個預(yù)定義的接口,從而使他們能夠?yàn)榭蚣芴峁┳远x的實(shí)現(xiàn)或擴(kuò)展。

核心目標(biāo):

  • 解耦: SPI 機(jī)制讓框架的核心與其擴(kuò)展部分保持解耦,使核心代碼不依賴于具體的實(shí)現(xiàn)。
  • 動態(tài)加載:系統(tǒng)能夠通過特定的機(jī)制(如 Java ServiceLoader )動態(tài)地發(fā)現(xiàn)和加載所需的實(shí)現(xiàn)。
  • 靈活性:框架用戶可以根據(jù)自己的需求選擇、更換或增加新的實(shí)現(xiàn),而無需修改核心代碼。

可插拔:第三方提供的服務(wù)或?qū)崿F(xiàn)可以輕松地添加到或從系統(tǒng)中移除,無需更改現(xiàn)有的代碼結(jié)構(gòu)。

價值:

  • 為框架或庫的用戶提供更多的自定義選項(xiàng)和靈活性。
  • 允許框架的核心部分保持穩(wěn)定,同時能夠容納新的功能和擴(kuò)展。

SPI與“開閉原則”

  “開閉原則”提倡軟件實(shí)體應(yīng)該對擴(kuò)展開放,但對修改封閉。即在不改變現(xiàn)有代碼的前提下,通過擴(kuò)展來增加新的功能。

SPI如何體現(xiàn)“開閉原則”:

對擴(kuò)展開放: SPI 提供了一種標(biāo)準(zhǔn)化的方式,使第三方開發(fā)者可以為現(xiàn)有系統(tǒng)提供新的實(shí)現(xiàn)或功能。

對修改封閉:添加新的功能或特性時,原始框架或庫的代碼不需要進(jìn)行修改。

獨(dú)立發(fā)展:框架與其 SPI 實(shí)現(xiàn)可以獨(dú)立地進(jìn)化和發(fā)展,互不影響。

  總之, SPI 是一種使軟件框架或庫更加模塊化、可擴(kuò)展和可維護(hù)的有效方法。通過遵循“開閉原則”, SPI 確保了系統(tǒng)的穩(wěn)定性和靈活性,從而滿足了不斷變化的業(yè)務(wù)需求。

到此這篇關(guān)于SPI機(jī)制在JDK與Spring Boot中的應(yīng)用的文章就介紹到這了,更多相關(guān)JDK與Spring Boot應(yīng)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MyBatis實(shí)現(xiàn)動態(tài)SQL的方法

    MyBatis實(shí)現(xiàn)動態(tài)SQL的方法

    動態(tài)SQL是MyBatis強(qiáng)大特性之一,極大的簡化我們拼裝SQL的操作,本文主要介紹了MyBatis實(shí)現(xiàn)動態(tài)SQL的方法,具有一定的參考價值,感興趣的可以了解一下
    2024-06-06
  • 使用Mybatis遇到的there is no getter異常

    使用Mybatis遇到的there is no getter異常

    這篇文章主要介紹了使用Mybatis遇到的there is no getter異常,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • Java手寫一個日志框架的示例代碼

    Java手寫一個日志框架的示例代碼

    日志框架是一種用于記錄和管理應(yīng)用程序運(yùn)行時信息的軟件組件,它通常提供了一套API讓開發(fā)人員能夠在代碼中插入日志語句,下面我們就來學(xué)習(xí)一下如何手寫一個日志框架吧
    2023-12-12
  • struts中動態(tài)方法調(diào)用使用通配符

    struts中動態(tài)方法調(diào)用使用通配符

    這篇文章主要介紹了struts中動態(tài)方法調(diào)用使用通配符的相關(guān)資料,非常不錯,具有參考借鑒價值,感興趣的朋友一起看看吧
    2016-09-09
  • Spring中的MultipartFile詳解

    Spring中的MultipartFile詳解

    這篇文章主要介紹了Spring中的MultipartFile詳解,隨著Spring框架的崛起,使用Spring框架中的MultipartFile來處理文件也是件很方便的事了,今天就為大家?guī)砥饰鯩ultipartFile的神秘面紗,需要的朋友可以參考下
    2024-01-01
  • Spark Streaming算子開發(fā)實(shí)例

    Spark Streaming算子開發(fā)實(shí)例

    這篇文章主要介紹了Spark Streaming算子開發(fā)實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 只需兩步實(shí)現(xiàn)Eclipse+Maven快速構(gòu)建第一個Spring Boot項(xiàng)目

    只需兩步實(shí)現(xiàn)Eclipse+Maven快速構(gòu)建第一個Spring Boot項(xiàng)目

    這篇文章主要介紹了只需兩步實(shí)現(xiàn)Eclipse+Maven快速構(gòu)建第一個Spring Boot項(xiàng)目,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • Springboot集成Mybatis-plus、ClickHouse實(shí)現(xiàn)增加數(shù)據(jù)、查詢數(shù)據(jù)功能

    Springboot集成Mybatis-plus、ClickHouse實(shí)現(xiàn)增加數(shù)據(jù)、查詢數(shù)據(jù)功能

    本文給大家講解Springboot + mybatis-plus 集成ClickHouse,實(shí)現(xiàn)增加數(shù)據(jù)、查詢數(shù)據(jù)功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • 詳解Java的Spring框架中的事務(wù)管理方式

    詳解Java的Spring框架中的事務(wù)管理方式

    這篇文章主要介紹了Java的Spring框架中的事務(wù)管理方式,Spring框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下
    2015-12-12
  • maven依賴包加載緩慢的原因以及解決方案

    maven依賴包加載緩慢的原因以及解決方案

    這篇文章主要介紹了maven依賴包加載緩慢的原因以及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評論