一、什么是SPI機(jī)制
SPI 全稱為 (Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制,可以輕松實(shí)現(xiàn)面向服務(wù)的注冊(cè)與發(fā)現(xiàn),完成服務(wù)提供與使用的解耦,并且可以實(shí)現(xiàn)動(dòng)態(tài)加載。
引入服務(wù)提供者就是引入了SPI接口的實(shí)現(xiàn)者,通過本地的注冊(cè)發(fā)現(xiàn)獲取到具體的實(shí)現(xiàn)類,輕松可插拔,SPI實(shí)際上是“基于接口的編程+策略模式+配置文件”組合實(shí)現(xiàn)的動(dòng)態(tài)加載,為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。
我的理解就是上層提供接口,我們需要去實(shí)現(xiàn),并且上層只需要根據(jù)我們的配置文件即可拿到我們的實(shí)現(xiàn)類(反射獲?。?。
二、SPI具體約定
2.1 Java SPI的具體約定為
當(dāng)服務(wù)的提供者,提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的文件。該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,就能通過該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化,完成模塊的注入。 基于這樣一個(gè)約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類,而不需要在代碼里制定。JDK提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類:java.util.ServiceLoader
2.2 JDK提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類
主程序通過java.util.ServiceLoder動(dòng)態(tài)裝載實(shí)現(xiàn)模塊,它通過掃描META-INF/services目錄下的配置文件找到 實(shí)現(xiàn)類的全限定名,把類加載到JVM。
2.3 SPI的理解舉例
假設(shè)JDK中支持音樂播放,且只支持mp3的播放,有些廠商想在這個(gè)基礎(chǔ)之上支持mp4播放,有些想支持mp5播放,而這些廠商都是第三方廠商,如果沒有提供SPI這種實(shí)現(xiàn)標(biāo)準(zhǔn),那就只有修改JAVA的源代碼了,那這個(gè)弊端也是顯而易見的,而有了SPI標(biāo)準(zhǔn),SUN公司只需要提供一個(gè)播放接口,在實(shí)現(xiàn)播放的功能上通過ServiceLoad的方式加載服務(wù),那么第三方只需要實(shí)現(xiàn)這個(gè)播放接口,再按SPI標(biāo)準(zhǔn)的約定進(jìn)行打包,再放到classpath下面就OK了,沒有一點(diǎn)代碼的侵入性。
三、應(yīng)用場(chǎng)景
3.1 JDBC驅(qū)動(dòng)
典型應(yīng)用,JDBC的實(shí)現(xiàn)機(jī)制。通常各大廠商(如MySQL、Oracle)會(huì)根據(jù)一個(gè)統(tǒng)一的規(guī)范(java.sql.Driver)開發(fā)各自的驅(qū)動(dòng)實(shí)現(xiàn)邏輯??蛻舳耸褂肑DBC時(shí)不需要去改變代碼,直接引入不同的SPI接口服務(wù)即可。MySQL的則是com.mysql.jdbc.Drive,Oracle則是oracle.jdbc.driver.OracleDriver

3.2 日志框架SLF4J
SLF4J,即Java的簡(jiǎn)單日志門面( Simple Logging Facade for Java SLF4J),作為一個(gè)簡(jiǎn)單的門面或抽象,用來服務(wù)于各種各樣的日志框架,比如java.util.logging、logback和log4j。SLF4J允許最終用戶在部署時(shí)集成自己想要的日志框架(SPI機(jī)制)。簡(jiǎn)單來說,SLF4J是Java日志的一個(gè)標(biāo)準(zhǔn)或規(guī)范,logging、logback和log4j是對(duì)該規(guī)范的具體實(shí)現(xiàn)。可參考從SLF4J談到依賴倒置原則、面向抽象編程、開閉原則



3.3 SpringBoot中的類SPI擴(kuò)展機(jī)制
在SpringBoot的自動(dòng)裝配過程中,最終會(huì)加載META-INF/spring.factories文件,而加載的過程是由SpringFactoriesLoader加載的。從CLASSPATH下的每個(gè)Jar包中搜尋所有META-INF/spring.factories配置文件,然后將解析properties文件,找到指定名稱的配置后返回。需要注意的是,其實(shí)這里不僅僅是會(huì)去ClassPath路徑下查找,會(huì)掃描所有路徑下的Jar包,只不過這個(gè)文件只會(huì)在Classpath下的jar包中。(例如:數(shù)據(jù)庫的自動(dòng)配置功能)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式為:key=value1,value2,value3
// 從所有的jar包中找到META-INF/spring.factories文件
// 然后從文件中解析出key=factoryClass類名稱的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得資源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍歷所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根據(jù)資源文件URL解析properties文件,得到對(duì)應(yīng)的一組@Configuration類
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 組裝數(shù)據(jù),并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
可以看到,它并沒有采用JDK中的SPI機(jī)制來加載這些類,不過原理差不多。都是通過一個(gè)配置文件,加載并解析文件內(nèi)容,然后通過反射創(chuàng)建實(shí)例。
假如你希望參與到SpringBoot初始化的過程中,現(xiàn)在我們又多了一種方式。我們也創(chuàng)建一個(gè)spring.factories文件,自定義一個(gè)初始化器。
org.springframework.context.ApplicationContextInitializer=com.youyouxunyin.config.context.MyContextInitializer

然后定義一個(gè)MyContextInitializer類
public class MyContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
System.out.println(configurableApplicationContext);
}
}
3.4 Dubbo中的應(yīng)用
我們熟悉的Dubbo也不例外,它也是通過 SPI 機(jī)制加載所有的組件。同樣的,Dubbo 并未使用 Java 原生的 SPI 機(jī)制,而是對(duì)其進(jìn)行了增強(qiáng),使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個(gè)非常重要的模塊?;?SPI,我們可以很容易的對(duì) Dubbo 進(jìn)行拓展。
參考文章
SPI機(jī)制的原理和應(yīng)用
Dubbo源碼分析(三)Dubbo中的SPI和自適應(yīng)擴(kuò)展機(jī)制
java之spi機(jī)制簡(jiǎn)介
Java SPI 實(shí)戰(zhàn)