個人自建博客地址
什么是SPI呢?
SPI全稱Service Provider Interface,翻譯過來就是服務提供者接口。調用方提供接口聲明,服務提供方對接口進行實現,提供服務的一種機制,服務提供方往往是第三方或者是外部擴展。
下面是一段java.util.ServiceLoader
類的注釋:
A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means.
翻譯:
服務提供者是對某一服務的具體實現。在服務提供者中的類一般會實現服務所定義的接口,并繼承服務本身定義的類。服務提供者可以通過擴展的方式安裝到Java平臺的實現中,也就是說,將jar文件放置到標準的擴展目錄之一。此外,服務提供者還可以通過將其添加到應用程序的類路徑,或者使用其他與平臺相關的手段來使其可用。
可以這樣理解,Java提供了一種機制可以幫我們務發現加載某個接口的實現類,實現類不在本模塊中,實現類可以由第三方提供,可以是依賴的jar或是其他擴展方式。
SPI的好處是什么?
SPI機制使用了接口,自然有接口的特點,面相接口編程,提供制定標準,實現由是實現者提供。
- **解耦和可擴展性:**SPI將接口與實現分離,我們就可以在不修改接口的情況下,輕松替換實現和新增新的實現,這也有利于模塊化開發的擴展。
- **標準化:**SPI提供了一種標準化的方式來定義和實現服務,這樣不同的開發者可以遵循相同的規則來提供和消費服務,減少了集成時的混亂和錯誤。
SPI原理
用一個示例畫一個SPI原理圖如下:
ServiceInterface
是一個定義了服務方法的接口。ServiceProviderA
和ServiceProviderB
是實現了ServiceInterface
的具體服務提供者。ServiceLoader
負責加載服務并調用。
Java SPI ServiceLoader工作流程
- 首先在服務調用者中有一個功能接口
A
- 第三方服務提供者作為插件模塊要實現這個功能,首先有一個實現類
com.test.AImpl
實現這個接口,然后在自己的模塊里的META-INF/services/
目錄下創建com.test.A文件,
,這里文件名是A接口的全限定名,文件內容就是com.test.AImpl
,也就是實現類的全限定名。
- 第三方服務提供者作為插件模塊要實現這個功能,首先有一個實現類
- 服務調用者使用ServiceLoader 創建加載器,根據接口精確遍歷
META-INF/services/
目錄對對應接口的實現類進行反射并實例化,這樣我們就可以獲得的根據A
接口的不同實現了。
接下來上示例代碼
定義服務接口:
package com.example.service;/*** 定義演出接口*/
public interface Perform {void show();
}
負責表演歌曲的服務提供者:
package com.example.serviceprovider1;public class Singer implements com.example.service.Perform {public void show() {System.out.println("表演歌曲節目");}
}
服務提供者所在jar中:
文件名:META-INF/services/com.test.A
內容:com.example.serviceprovider1.Singer
負責表演舞蹈的服務提供者:
package com.example.serviceprovider;import com.example.service.Perform;/*** 舞者提供才藝目*/
public class Dancer implements Perform {public void show() {System.out.println("表演跳舞節目");}}
服務提供者所在jar中:
文件名:META-INF/services/com.test.A
內容:com.example.serviceprovider.Dancer
調用者:
package com.example.serviceuser;import com.example.service.Perform;import java.util.Iterator;
import java.util.ServiceLoader;public class MainTestSpi {public static void main(String[] args) {ServiceLoader<Perform> serviceLoader = ServiceLoader.load(Perform.class);Iterator<Perform> iterator = serviceLoader.iterator();while (iterator.hasNext()) {Perform perform = iterator.next();perform.show();}}
}
調用結果:
表演跳舞節目
表演歌曲節目
不同框架的SPI思想實現之JDBC
我們先說JDBC中的SPI機制實現
- JDK中
java.sql.Driver
接口定義了定義了驅動與數據庫交互的標準方法。 - 不同的數據庫廠商提供具體的驅動實現類,例如MySQL驅動實現了
Driver
接口的connect()
方法,用于建立數據庫連接。 - 每個驅動JAR包的
META-INF/services
目錄下需創建一個以接口全限定名(如java.sql.Driver
)命名的文件,文件內容為實現類的全限定名(如com.mysql.cj.jdbc.Driver
)。 - ServiceLoader通過此文件發現并加載驅動。
這是Mysql JDBC通過SPI機制注冊驅動的核心代碼:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {//// Register ourselves with the DriverManager//static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}/*** Construct a new driver and register it with DriverManager* * @throws SQLException* if a database error occurs.*/public Driver() throws SQLException {// Required for Class.forName().newInstance()}
}
當SerciceLoader的Iterator
調用next()
方法時,就會觸發java.sql.DriverManager.registerDriver(new Driver());
對數據庫廠商的驅動進行注冊。
加載和注冊驅動的過程如下:
Spring Boot 自動裝配也體現了SPI思想
自動裝配是 SPI 的“升級版”
- 隱式接口:用注解和文件約定替代顯式接口,降低侵入性。
- 動態加載:通過條件注解實現按需裝配,而非一次性加載所有實現類。
- 開箱即用:通過 Starter 依賴傳遞,開發者只需關注業務邏輯,無需手動配置。
自動裝配流程
-
Spring Boot 通過
SpringFactoriesLoader
掃描所有依賴中的以下文件:-
舊方式:
META-INF/spring.factories
(鍵為EnableAutoConfiguration
)。 -
新方式(Spring Boot 2.7+):
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
。
-
# AutoConfiguration.imports
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
...
-
過濾和排序配置類
-
去重與過濾:排除重復的配置類,并根據
@AutoConfigureOrder
、@Order
注解排序。 -
排除不需要的配置:通過
spring.autoconfigure.exclude
配置或@EnableAutoConfiguration(exclude=...)
排除特定配置類。
-
-
條件化評估(Conditional Evaluation)
Spring Boot 通過 @Conditional
系列注解 動態決定是否啟用某個配置類或 Bean。常見的條件注解包括:
注解 | 作用 |
---|---|
@ConditionalOnClass | 類路徑中存在指定類時生效。 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean 時生效。 |
@ConditionalOnProperty | 配置文件中存在指定屬性且匹配值時生效。 |
@ConditionalOnWebApplication | 應用是 Web 應用時生效。 |
示例:
java
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // 存在 DataSource 類時生效
public class DataSourceAutoConfiguration {@Bean@ConditionalOnMissingBean // 容器中沒有 DataSource Bean 時生效public DataSource dataSource() {return new HikariDataSource();}
}
- 加載并注冊 Bean
通過條件評估的配置類中的 @Bean
方法會被執行,生成的 Bean 實例注冊到 Spring 容器中。
-
自動裝配的優先級
-
用戶自定義 Bean 優先:如果用戶手動定義了某個 Bean(如
@Bean
方法),自動配置的 Bean 會被跳過(由@ConditionalOnMissingBean
控制)。 -
配置類加載順序:通過
@AutoConfigureOrder
或@Order
控制配置類的執行順序(值越小優先級越高)。
-
自動裝配的觸發時機
自動裝配在 Spring 容器的 refresh()
階段完成,具體步驟如下:
- 準備環境(Environment):加載配置文件(如
application.properties
)。 - 創建
BeanFactory
:初始化 Spring 容器的 Bean 工廠。 - 執行
BeanFactoryPostProcessor
:處理 Bean 工廠的后期處理(如解析@Configuration
類)。 - 加載自動配置類:通過
AutoConfigurationImportSelector
選擇并加載符合條件的配置類。 - 注冊 Bean 定義:將自動配置類中的 Bean 定義注冊到容器。
- 實例化單例 Bean:完成所有 Bean 的初始化。
因此,Spring Boot 自動裝配雖然沒有傳統意義上的接口,但通過標準化約定和條件化注解,更靈活地實現了 SPI 的核心思想:解耦服務提供者與消費者,實現模塊化擴展。
Java 自身的SPI通過ServiceLoader
實現,使用起來簡單,但是沒有條件過濾,不便于按需加載。Spring Boot的自動裝配,Dubbo的掃描擴展等其他框架都根據自身需求實現了更好用的SPI服務加載流程。
SPI思想的應用場景
SPI(服務提供者接口)主要用于解耦接口與實現,支持模塊化,插件化擴展。典型場景包括:
-
框架擴展:如JDBC驅動加載(Java SPI)、Dubbo的協議擴展(自適應SPI)。
-
插件系統:日志組件(Log4j2的
@Plugin
)。 -
配置自動化:Spring Boot Starter通過
AutoConfiguration.imports
實現“開箱即用”。 -
服務治理:微服務中動態加載服務發現(如Spring Cloud)、配置中心擴展。
-
跨平臺適配:如SLF4J綁定不同日志實現,屏蔽底層差異。
SPI通過約定發現+動態加載,提升系統靈活性和可維護性。