Spring SPI和Spring Boot SPI - 第345篇

2021-03-05 SpringBoot

相關歷史文章(閱讀本文前,您可能需要先看下之前的系列👇

國內最全的Spring Boot系列之四

DriverManager SPI分析和Java SPI原理  - 第344篇

Java SPI一探究竟 - 第343篇

前言

       在前面的章節《DriverManager SPI分析和Java SPI原理》中,我們通過分析DriverManager的SPI和Java SPI的原理,對於SPI有了一個比較深的了解。現在我們大部分的項目都是基於Spring或者Spring Boot開發的,所以這一節我們談談SPI在Spring | Spring Boot中的應用。

 

一、Spring SPI

1.1執行流程

Spring中使用的類是SpringFactoriesLoader,在org.springframework.core.io.support包中,大體的執行流程:

(1)SpringFactoriesLoader 會掃描 classpath 中的 META-INF/spring.factories文件。

(2)SpringFactoriesLoader 會加載並實例化META-INF/spring.factories 中的指定類型。

(3)META-INF/spring.factories 內容必須是 properties 的Key-Value形式,多值以逗號隔開。

 

1.2使用

       我們來看一個小小的例子來理解下上面的流程:

(1)創建META-INF/spring.factories文件

       在META-INF下創建spring.factories文件,在文件中配置如下信息:

com.kfit.id.IdGenerator=\  com.kfit.id.impl.UUIDIdGenerator,\  com.kfit.id.impl.TimestampGenerato

       然後在測試代碼中使用SpringFactoriesLoader進行加載:

public static void main(String[] args) {    List<IdGenerator> idGeneratorList =  SpringFactoriesLoader.loadFactories(IdGenerator.class,null);    List<String> classNameList =  SpringFactoriesLoader.loadFactoryNames(IdGenerator.class,null);
System.out.println(idGeneratorList); System.out.println(classNameList);}

說明:

(1)spring.factories文件的key可以是接口,也可以是抽象類、具體的類。但是有個原則:=後面必須是key的實現類(子類)。

(2)在spring.factories文件中可以指定多個key,對於java spi如果多個接口的話,需要配置多個文件。

(3)key還可以是註解,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,它就是一個註解。

(4)文件的格式需要保證正確,否則會返回[](不會報錯)。

(5)=右邊不能是抽象類,必須能夠實例化。且有空的構造函數~

(6)loadFactories依賴方法loadFactoryNames。loadFactoryNames方法只拿全類名,loadFactories拿到全類名後會立馬實例化

(7)loadFactories實例化完成所有實例後,會調用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的,這個特點特別的重要。

 

1.3原理

       核心加載類是SpringFactoriesLoader,主要暴露了兩個方法:loadFactories和loadFactoryNames:

public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

private SpringFactoriesLoader() { }


public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { Assert.notNull(factoryType, "'factoryType' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames); } List<T> result = new ArrayList<>(factoryImplementationNames.size()); for (String factoryImplementationName : factoryImplementationNames) { result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; }

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; }
try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
@SuppressWarnings("unchecked") private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) { try { Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader); if (!factoryType.isAssignableFrom(factoryImplementationClass)) { throw new IllegalArgumentException( "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException( "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", ex); } }
}

       我們從源碼當中可以看到,loadFactories會調用loadFactoryNames,然後在loadFactoryNames方法中調用了方法loadSpringFactories:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {   MultiValueMap<String, String> result = cache.get(classLoader);   if (result != null) {      return result;   }
try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement(); UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); }}

       具體這裡的注釋已經寫的很詳細了,就不再過多進行說明了。

 

二、Spring-Boot-Starter

       SpringBoot的 @SpringBootApplication 註解,裡面還有一個 @EnableAutoConfiguration 註解,開啟自動配置的。

       可以發現這個自動配置註解在另一個工程,而這個工程裡也有個spring.factories文件,如下圖:

       我們知道在SpringBoot的目錄下有個spring.factories文件,裡面都是一些接口的具體實現類,可以由SpringFactoriesLoader加載。

那麼同樣的道理,spring-boot-autoconfigure模塊也能動態加載了。看一下其中的內容:

org.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
org.springframework.context.ApplicationListener=\org.springframework.boot.autoconfigure.BackgroundPreinitializer
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\org.springframework.boot.autoconfigure.condition.OnBeanCondition,\org.springframework.boot.autoconfigure.condition.OnClassCondition,\org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\

       在這裡面配置了PropertySourceLoader和ApplicationListener等接口的具體實現類,然後通過SpringFactoriesLoader這個類去加載這個文件,並獲得具體的類路徑。

 

我就是我,是顏色不一樣的煙火。
我就是我,是與眾不同的小蘋果。

à悟空學院:https://t.cn/Rg3fKJD

學院中有Spring Boot相關的課程!點擊「閱讀原文」進行查看!

SpringBoot視頻:http://t.cn/A6ZagYTi

SpringBoot交流平臺:https://t.cn/R3QDhU0

SpringSecurity5.0視頻:http://t.cn/A6ZadMBe

ShardingJDBC分庫分表:http://t.cn/A6ZarrqS

分布式事務解決方案:http://t.cn/A6ZaBnIr

JVM內存模型調優實戰:http://t.cn/A6wWMVqG

Spring入門到精通:https://t.cn/A6bFcDh4

大話設計模式之愛你:https://dwz.cn/wqO0MAy7

相關焦點

  • 快速創建 Spring Cloud 應用的 Spring Initializr 使用及原理
    組件,自動添加 spring-boot-starter-{id} 作為生成的依賴坐標。項目,你會在日誌裡發現這樣的輸出「 Fetching boot metadata from spring.io/project_metadata/spring-boot 」為了避免過於頻繁的檢查 Spring Boot 版本,官方是建議配合緩存一起使用。
  • Spring Boot中使用Mockito進行Web測試 - 第339篇
    篇SpringBoot框架開發的優秀的項目「值得收藏學習」 - 第335從Spring整合第三方框架學習Spring Boot - 第336篇Mock工具之Mockito - 第337篇Spring Boot中使用Mockito - 第338篇悟纖:師傅,我上面mock的service層和dao層的例子?
  • Spring Boot 和 Spring 到底有啥區別?
    Web應用程式:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.6.RELEASE<
  • Spring Boot 2.4 手工和 SDKMAN! 安裝 Spring Boot 命令行
    但是 Spring Boot 的官方手冊中有些這方面的內容和介紹,因此我們也在這裡對這部分的內容進行了一些說明。Spring 工具被用來初始化 Spring 項目和一些其他的工作,但 Spring 是基於 Java 的,因此很多東西需要自己配置環境。其實自己配置環境比使用環境配置工具要好一些,能夠讓你了解有關運行的參數和配置。
  • Spring 和 Spring Boot 之間到底有啥區別?
    ><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.0.6.RELEASE</version></dependency
  • Spring Boot Admin 2.2.4 發布,兼容最新版本 Spring Boot
    spring boot admin 2.2.4 版本發布,本版本為 bug 修復版本 主要兼容 spring boot 2.3.x。
  • Spring 和 Spring Boot 最核心的 3 大區別,詳解!
    <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId>    <version>2.0.6.RELEASE<
  • Spring Boot 常見錯誤及解決方法
    錯誤分析: Spring Boot 的正常 jar 包運行方是通過 spring-boot-loader 這個模塊裡的 JarLauncher 完成的,該類內部提供了一套運行的規範。解決方案: 在 pom 裡加上 spring-boot-maven-plugin 的 maven 插件配置 (該插件會在 jar 裡加入 spring-boot-loader 的代碼,並在 MANIFEST.MF 中的 Main-Class 裡寫入 JarLauncher):<plugin
  • spring boot 整合shiro 錯誤
    最近在弄spring boot 整合shiro的。凱哥現在用的是spring boot。web.xml沒有。但是凱哥配置了shiro的核心攔截器啊。如下圖:並且使用了@Configuration這個註解了。
  • Spring Boot集成JDBCTemplate
    JDBCTemplate相對於其他ORM框架來說是極其簡單和極容易上手的一個資料庫連接的封裝。在學習JDBCTemplate之前,我們先來了解一下JDBC相關的概念和操作。JDBC簡介Java資料庫連接,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範客戶端程序如何來訪問資料庫的應用程式接口,提供了諸如查詢和更新資料庫中數據的方法。它由一組用Java語言編寫的類和接口組成。通常說的JDBC是面向關係型資料庫的。
  • Spring Boot 整合 Thymeleaf
    >在新建項目時添加,在 Templeate Engines 中勾選 Thymeleaf;對於未添加 Thymeleaf 依賴的項目,直接在 pom.xml 中手動添加依賴即可;<dependency><groupId>org.springframework.boot
  • 2021 最新版 Spring Boot 速記教程
    <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-aop</artifactId></dependency>
  • Spring Boot 的單元測試和集成測試
    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency
  • Spring Boot 啟動事件和監聽器,太強大了!
    比如我們想在上面的第 8 步,即應用啟動完成可以接收請求了,我們簡單輸出一個成功標識。>import lombok.extern.slf4j.Slf4j; import org.springframework.boot.availability.AvailabilityChangeEvent; import org.springframework.boot.availability.ReadinessState
  • Spring Boot中Tomcat、Jetty、Undertow的使用
    Tomcat在我們使用SpringBoot開發WebApi時,會引入spring-boot-starter-web這個starter組件,其自帶了Tomcat容器,所以我們平時新建項目啟動起來,會看見Tomcat相關的一些信息。
  • Spring Boot 集成undertow作為web容器
    由於Undertow的性能和內存使用方面都優於Tomcat,所以現在很多SpringBoot項目放棄了Tomcat,選擇了Undertow。一、先去除tomcat的引用<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web
  • Postgres、R2DBC、Spring Data JDBC和Spring WebFlux的響應式API簡介
    看看這篇文章,我們將探索使用R2DBC,Spring Data JDBC等構建響應式Web應用程式。我知道 - 標題中已列出很多技術。 Spring WebFlux,作為構建響應堆棧Web應用程式的項目,已經被Spring 5和Spring Boot 2中引入。
  • 江帥帥:精通 Spring Boot 系列 03
    具體的配置參數可以參考官網文檔第 10 章 Appendices:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/#common-application-properties4.
  • Spring Boot 單元測試
    一、 單元測試的概念概念:單元測試(unit testing),是指對軟體中的最小可測試單元進行檢查和驗證。在Java中單元測試的最小單元是類。單元測試是開發者編寫的一小段代碼,用於檢驗被測代碼的一個很小的、很明確的功能是否正確。執行單元測試,就是為了證明這 段代碼的行為和我們期望是否一致。
  • 使用IntelliJ IDEA創建一個Maven的Spring Boot項目
    關於maven的安裝和配置參考:</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent>