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

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

 更新時(shí)間: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è)計(jì)允許核心庫/框架在不修改自身代碼的情況下,通過第三方實(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) 會返回一個(gè)實(shí)現(xiàn)了 MyServiceInterface 的實(shí)例迭代器。
  • 缺點(diǎn)JDK 原生的 SPI 每次通過 ServiceLoader 加載時(shí)都會初始化一個(gè)新的實(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)境的不同來選擇加載哪個(gè)數(shù)據(jù)庫驅(qū)動。
  • 配置Spring 允許通過 spring.factories 文件在 META-INF 目錄下進(jìn)行配置,這與 JDK SPI 很相似,但它提供了更多的功能和靈活性。

舉個(gè)類比的例子:

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

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

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

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

全部代碼和步驟如下:

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

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

步驟2:為服務(wù)接口提供實(shí)現(xiàn),這里會提供兩個(gè)簡單的實(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è)備。每個(gè)設(shè)備都有自己的功能和特性,但都遵循相同的 USB 標(biāo)準(zhǔn)。

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

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

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ù)提供者時(shí),它會特意去查找這個(gè)路徑下的文件。

  請確保文件的每一行只有一個(gè)名稱,并且沒有額外的空格或隱藏的字符,文件使用 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 接口提供的兩個(gè)實(shí)現(xiàn),并且我們可以在不修改 Main 類的代碼的情況下,通過添加更多的實(shí)現(xiàn)類和更新 META-INF/services/com.example.MessageService 文件來擴(kuò)展我們的服務(wù)。

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

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

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

   Spring 官方在其文檔和源代碼中多次提到了 SPI Service Provider Interface )的概念。但是,當(dāng)我們說“ Spring SPI ”時(shí),通常指的是 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 的一個(gè)特性,允許開發(fā)者自定義自動配置。通過 spring.factories 文件,開發(fā)者可以定義自己的自動配置類,這些類在 Spring Boot 啟動時(shí)會被自動加載。
  • 在這種情況下, SpringFactoriesLoader 的使用,尤其是通過 spring.factories 文件來加載和實(shí)例化定義的類,可以看作是一種特定的 SPI 實(shí)現(xiàn)方式,但它特定于 Spring Boot 。

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

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

提供兩個(gè)簡單的實(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)在,每一個(gè) MessageService 實(shí)現(xiàn)都被 BeanPostProcessor 處理了,添加了額外的消息 “[Processed by Spring SPI]” 。這演示了 Spring SPI 概念,通過 BeanPostProcessor 來擴(kuò)展或修改 Spring 容器中的 bean 。

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

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

  • 電視機(jī)與USB插口: 在這個(gè)新的示例中,電視機(jī)仍然是核心的 Spring 應(yīng)用程序,具體來說是 DemoApplication 類。這個(gè)核心應(yīng)用程序需要從某個(gè)服務(wù)(即 MessageService )獲取并打印一條消息。
  • USB插口: 與之前一樣, MessageService 接口就是這個(gè)" USB 插口"。它為電視機(jī)提供了一個(gè)標(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)。一個(gè)顯示 “Hello from HelloMessageService!” ,另一個(gè)顯示 “Hi from HiMessageService!” 。
  • BeanPostProcessor: 這是一個(gè)特殊的“魔法盒子”,可以將其視為一個(gè)能夠攔截并修改電視機(jī)顯示內(nèi)容的智能設(shè)備。當(dāng)插入 USB 設(shè)備(即 MessageService 的實(shí)現(xiàn))并嘗試從中獲取消息時(shí),這個(gè)“魔法盒子”會介入,并為每條消息添加 “[Processed by Spring SPI]” 。
  • Spring上下文配置: 這依然是電視機(jī)的使用說明書,但現(xiàn)在是使用了基于 Java 的配置方式,即 MessageServiceConfig 類。這個(gè)“使用說明書”指導(dǎo) Spring 容器如何創(chuàng)建并管理 MessageService 的實(shí)例,并且還指導(dǎo)它如何使用“魔法盒子”(即 MessageServicePostProcessor )來處理消息。

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

3.2 Spring Boot中的SPI思想

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

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

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

定義接口

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

這里會提供兩個(gè)簡單的實(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)建一個(gè)文件名為 spring.factories 。這個(gè)文件里,可以注冊 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)類的全路徑。如果有多個(gè)實(shí)現(xiàn)類,它們應(yīng)當(dāng)用逗號分隔。

   spring.factories 文件中的條目鍵和值之間不能有換行,即 key=value 形式的結(jié)構(gòu)必須在同一行開始。但是,如果有多個(gè)值需要列出(如多個(gè)實(shí)現(xiàn)類),并且這些值是逗號分隔的,那么可以使用反斜杠( \ )來換行。 spring.factories 的名稱是約定俗成的。如果試圖使用一個(gè)不同的文件名,那么 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 的第二個(gè)參數(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 插口是一個(gè)標(biāo)準(zhǔn)的接口,它允許連接各種設(shè)備,就像 MessageService 接口允許有多種實(shí)現(xiàn)方式。
  • USB設(shè)備(如U盤或移動硬盤): 這代表我們的服務(wù)實(shí)現(xiàn),例如 HelloMessageService HiMessageService 。每個(gè) USB 設(shè)備在插入電視機(jī)后都有特定的內(nèi)容或功能,這就像我們的每個(gè)服務(wù)實(shí)現(xiàn)返回不同的消息。
  • 電視機(jī)的USB設(shè)備目錄: 這是 spring.factories 文件。當(dāng)我們將 USB 設(shè)備插入電視機(jī)時(shí),電視機(jī)會檢查設(shè)備的信息或內(nèi)容, spring.factories 文件告訴 Spring Boot 哪些服務(wù)實(shí)現(xiàn)是可用的,就像電視機(jī)知道有哪些 USB 設(shè)備被插入。
  • 電視機(jī)的USB掃描功能: 這就是 SpringFactoriesLoader 。當(dāng)我們要從電視機(jī)上查看 USB 內(nèi)容時(shí),電視機(jī)會掃描并顯示內(nèi)容。同樣,當(dāng) DemoApplication 運(yùn)行時(shí), 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)行時(shí),它使用 SpringFactoriesLoader USB 掃描功能)來檢查哪些服務(wù)( USB 設(shè)備)是可用的,并輸出相應(yīng)的消息(顯示 USB 內(nèi)容)。

  總結(jié):在這個(gè) 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 引入了一個(gè)特性,允許驅(qū)動自動注冊到 DriverManager 。這是通過使用 Java SPI 來實(shí)現(xiàn)的。驅(qū)動 jar 包內(nèi)會有一個(gè) META-INF/services/java.sql.Driver 文件,此文件中包含了該驅(qū)動的 Driver 實(shí)現(xiàn)類的全類名。這樣,當(dāng)類路徑中有 JDBC 驅(qū)動的 jar 文件時(shí), 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ù)庫連接時(shí),背后正是利用 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ū)動程序中有一個(gè)類似于以下的類:

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

直接上圖:

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

對于 MySQL 的驅(qū)動程序,可以在其 JAR 文件的 META-INF/services 目錄下找到一個(gè)名為 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) 時(shí), DriverManager 會使用 ServiceLoader 來查找所有已注冊的 java.sql.Driver 實(shí)現(xiàn)。然后,它會嘗試每一個(gè)驅(qū)動程序,直到找到一個(gè)可以處理給定 jdbcUrl 的驅(qū)動程序。

以下是一個(gè)簡單的示例,展示如何使用 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();
}
}
}

  在上述代碼中,我們沒有明確指定使用哪個(gè) 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 對象,這個(gè)對象管理數(shù)據(jù)庫連接。實(shí)際上,添加 JDBC 驅(qū)動依賴時(shí), 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)建一個(gè)簡化的實(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();
}
}

  這個(gè)類提供兩個(gè)條件性的 beans (組件),分別是 SmsService EmailService 。這些 beans 的創(chuàng)建取決于 application.properties 文件中特定的屬性值。

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

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

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

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

spring.factories文件

src/main/resources/META-INF 目錄下創(chuàng)建一個(gè) 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)建了一個(gè) MessageService 接口和兩個(gè)實(shí)現(xiàn)( SmsService EmailService )。然后,我們創(chuàng)建了一個(gè)自動配置類,其中包含兩個(gè) bean 定義,這兩個(gè) bean 定義分別基于 application.properties 中的屬性值條件性地創(chuàng)建。在 spring.factories 文件中,我們聲明了這個(gè)自動配置類,以便 Spring Boot 在啟動時(shí)能夠自動加載它。

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

電視機(jī)類比

1.總體概念

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

3.Spring Boot的自動配置

  • 現(xiàn)在,想象一下現(xiàn)代的智能電視。當(dāng)插入一個(gè)設(shè)備,電視機(jī)不僅可以識別它,還可能根據(jù)所連接的設(shè)備類型自動調(diào)整設(shè)置,例如選擇正確的輸入源、優(yōu)化圖像質(zhì)量等。
  • 這就像 Spring Boot 的自動配置:當(dāng) Spring Boot 應(yīng)用啟動時(shí),它會檢查 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è)置功能為我們提供了一個(gè)直觀的方式來理解 Java SPI 機(jī)制和 Spring Boot 的自動配置如何工作,以及它們?nèi)绾螢閼?yīng)用開發(fā)者提供便利。

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

   SPI ,即服務(wù)提供者接口,是一種特定的設(shè)計(jì)模式。它允許框架或核心庫為第三方開發(fā)者提供一個(gè)預(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)。

價(jià)值:

  • 為框架或庫的用戶提供更多的自定義選項(xiàng)和靈活性。
  • 允許框架的核心部分保持穩(wěn)定,同時(shí)能夠容納新的功能和擴(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)或功能。

對修改封閉:添加新的功能或特性時(shí),原始框架或庫的代碼不需要進(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的方法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-06-06
  • 使用Mybatis遇到的there is no getter異常

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

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

    Java手寫一個(gè)日志框架的示例代碼

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

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

    這篇文章主要介紹了struts中動態(tài)方法調(diào)用使用通配符的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧
    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í)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 只需兩步實(shí)現(xiàn)Eclipse+Maven快速構(gòu)建第一個(gè)Spring Boot項(xiàng)目

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

    這篇文章主要介紹了只需兩步實(shí)現(xiàn)Eclipse+Maven快速構(gòu)建第一個(gè)Spring Boot項(xiàng)目,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    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依賴包加載緩慢的原因以及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10

最新評論