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

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

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

1. SPI解讀:什么是SPI?

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

1.JDK原生的SPI

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

2.Spring的SPI

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

舉個類比的例子:

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

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

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

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

全部代碼和步驟如下:

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

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

步驟2:為服務(wù)接口提供實現(xiàn),這里會提供兩個簡單的實現(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!";
}
}

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

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

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

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

   META-INF/services/ Java SPI ( Service Provider Interface ) 機制中約定俗成的特定目錄。它不是隨意選擇的,而是 SPI 規(guī)范中明確定義的。因此,當使用 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());
}
}
}

運行結(jié)果如下:

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

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

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

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

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

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

1.Spring的SPI

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

2.Boot的spring.factories機制

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

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

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

提供兩個簡單的實現(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());
}
}

運行結(jié)果:

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

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

類比文章開頭的電視機的例子:

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

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

3.2 Spring Boot中的SPI思想

   Spring Boot 有一個與 SPI 相似的機制,但它并不完全等同于 Java 的標準 SPI

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

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

定義接口

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

這里會提供兩個簡單的實現(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 實現(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 是實現(xiàn)類的全路徑。如果有多個實現(xiàn)類,它們應(yīng)當用逗號分隔。

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

這里 spring.factories 又可以寫為

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

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

使用 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ù)是類加載器,此處我們使用默認的類加載器,所以傳遞 null 。

運行結(jié)果:

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

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

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

簡化解釋:

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

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

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

  數(shù)據(jù)庫驅(qū)動的 SPI 主要體現(xiàn)在 JDBC 驅(qū)動的自動發(fā)現(xiàn)機制中。 JDBC 4.0 引入了一個特性,允許驅(qū)動自動注冊到 DriverManager 。這是通過使用 Java SPI 來實現(xiàn)的。驅(qū)動 jar 包內(nèi)會有一個 META-INF/services/java.sql.Driver 文件,此文件中包含了該驅(qū)動的 Driver 實現(xiàn)類的全類名。這樣,當類路徑中有 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)用程序所使用。

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

以下是 SPI 機制的具體工作方式:

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

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

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

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

public class com.mysql.cj.jdbc.Driver implements java.sql.Driver {
// 實現(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ù)

  當我們調(diào)用 DriverManager.getConnection(jdbcUrl, username, password) 時, DriverManager 會使用 ServiceLoader 來查找所有已注冊的 java.sql.Driver 實現(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ū)動程序,因為 DriverManager 會自動為我們選擇合適的驅(qū)動程序。

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

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

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

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

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

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

MessageService接口

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

SMS服務(wù)實現(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ù)實現(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”)

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

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

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

運行結(jié)果:

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

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

電視機類比

1.總體概念

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

2.Java的SPI

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

3.Spring Boot的自動配置

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

4.擴展性

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

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

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

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

核心目標:

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

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

價值:

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

SPI與“開閉原則”

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

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

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

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

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

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

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

相關(guān)文章

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

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

    動態(tài)SQL是MyBatis強大特性之一,極大的簡化我們拼裝SQL的操作,本文主要介紹了MyBatis實現(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)用程序運行時信息的軟件組件,它通常提供了一套API讓開發(fā)人員能夠在代碼中插入日志語句,下面我們就來學習一下如何手寫一個日志框架吧
    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ā)實例

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

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

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

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

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

    本文給大家講解Springboot + mybatis-plus 集成ClickHouse,實現(xiàn)增加數(shù)據(jù)、查詢數(shù)據(jù)功能,本文通過實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    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

最新評論