最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot源碼閱讀之spring.factories的加載機(jī)制詳解

 更新時間:2025年11月19日 10:12:57   作者:Java小胡  
Spring Boot通過`spring.factories`文件實(shí)現(xiàn)自動裝配,該文件位于`META-INF`目錄下,Spring Boot在啟動時會讀取該文件并實(shí)例化其中配置的實(shí)現(xiàn)類

spring.factories的加載

提到 SpringBoot 的自動裝配,不管是文章還是視頻,都會提到 spring.factories 這個文件,這篇文章就來簡單講講 spring.factories 的作用,以及它是怎么被加載的

位置

以 SpringBoot 本體為例,spring.factories 在 jar 包的 META-INF 目錄下

其他第三方庫例如 mybatis-spring-boot-starter 等也都遵循這個規(guī)則

之所以要放在這個目錄下,是 SpringBoot 提前約定好的,在 SpringFactoriesLoader 中有這樣一個常量,項(xiàng)目啟動時,掃描的就是這個路徑:

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

類型

.factories 是 SpringBoot 專屬的類型,但從文件的格式來看,其實(shí)就是一個 .properties 文件

實(shí)際上 SpringBoot 在讀取解析 spring.factories 文件時,用的就是 properties 的解析器

內(nèi)容

截取一部分如下

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

反斜杠 \ 可以換行,并且新的一行依舊屬于上面那行

所以上面截取的這部分信息,實(shí)際只有兩條數(shù)據(jù)

等號 = 左邊的 key 是接口(也可以是注解)的全限定名,等號 = 右邊的 value 是以逗號分隔的實(shí)現(xiàn)類的全限定名

作用

先講講 SpringBoot 自己的 spring.factories,其中定義了各種接口的實(shí)現(xiàn)類,在 SpringBoot 運(yùn)行的某個過程中,會需要用到一些接口的功能,這時候就會從 spring.factories 中獲取對應(yīng)的實(shí)現(xiàn)類并實(shí)例化來進(jìn)行調(diào)用

好處是極大地降低了耦合度

當(dāng)版本升級的時候,只要修改 spring.factories 中的內(nèi)容,而不用修改代碼,就可以替換實(shí)現(xiàn)類

舉個例子,SpringBoot 的生命周期中有監(jiān)聽器的參與,同一階段,可能會觸發(fā)好幾個監(jiān)聽器的事件,而將來如果某個版本的 SpringBoot 要對“啟動完成”這個階段添加一個監(jiān)聽器,并作出一些處理,那么只需要寫好這個新的監(jiān)聽器,然后加入 spring.factories 即可,不用修改 SpringBoot 原本的代碼,并且原本的代碼會從 spring.factories 讀取到這個新的監(jiān)聽器并進(jìn)行事件傳播

當(dāng)然,這是只針對 SpringBoot 自身而言的作用

對用戶,或者第三方插件提供商,它的作用類似于 Java 的 SPI 機(jī)制

SPI 的全名是 Service Provider Interface,大概意思是,JDK 只提供某個模塊的接口規(guī)范,而具體實(shí)現(xiàn)由廠商來完成

耳熟能詳?shù)挠?JDBC 模塊、日志模塊等等

以 JDBC 為例,初學(xué) JDBC 時肯定很熟悉一行代碼:

Class.forName("com.mysql.cj.jdbc.Driver");

我們使用廠商提供的 JDBC 實(shí)現(xiàn),都需要手動去注冊并實(shí)例化

而 spring.factories 相比于 SPI,還提供了服務(wù)發(fā)現(xiàn)的機(jī)制,那就是我們只需要導(dǎo)入第三方庫的 starter,SpringBoot 就能自動掃描并且?guī)椭覀冏詾?bean,相信只要熟悉 SpringBoot,就不用我多講了

那么作為第三方庫的開發(fā)者需要做些什么呢

除了在 META-INF 目錄下留存一個 spring.factories 文件外,在文件內(nèi)容中,還要加上其自身的自動配置類

以 mybatis 為例:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

key 是 SpringBoot 的 @EnableAutoConfiguration 注解,這個注解就是自動裝配的關(guān)鍵,這篇文章的內(nèi)容注重于 spring.factories 的加載,關(guān)于自動裝配將寫在下一篇文章

加載

從啟動類的 run 方法進(jìn)入,我們第一次見到與 factories 相關(guān)的代碼是在 SpringApplication 的構(gòu)造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 下面三行都調(diào)用了 getSpringFactoriesInstances 方法,從方法名可以大致理解到作用是獲取 factories 文件中某個接口的實(shí)現(xiàn)類
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // loadFactoryNames 方法獲取 spring.factories 定義的對應(yīng)接口的所有實(shí)現(xiàn)類的全限定名
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 根據(jù)全限定名,和參數(shù)類型,實(shí)例化上面獲取到的那些類
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 根據(jù) @Order 注解排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

SpringFactoriesLoader#loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    // loadSpringFactories 是 spring.factories 加載的核心方法
    // 返回值是一個 Map,包含 spring.factories 所有的鍵值對,key 是接口名,value 是實(shí)現(xiàn)類的名字?jǐn)?shù)組
    // 這里獲取到所有鍵值對后,根據(jù)需要的接口名稱,獲取到相應(yīng)的實(shí)現(xiàn)數(shù)組
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

SpringFactoriesLoader#loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // cache 是這個類的靜態(tài)變量,所以是唯一的
    Map<String, List<String>> result = cache.get(classLoader);
    // 如果緩存中已經(jīng)有了,那么直接返回
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        // 使用 classLoader 從 classpath 讀取文件
        // 這個地址常量在上面已經(jīng)貼出來過了,也就是 META-INF/spring.factories
        // 如果引用了第三方庫,那么就會獲取到很多 url
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        // 所以要循環(huán)遍歷每一個庫里的 spring.factories 文件
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 用 properties 解析器讀取文件
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            // 下面的內(nèi)容就是把文件中的每一行數(shù)據(jù),變成 result 數(shù)據(jù),也就是 key(文件#String) 變成 key(Map#String)
            // value(文件#String) 變成 value(Map#List<String>)
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                // 這個 commaDelimitedListToStringArray 就是以逗號為分隔符,解析成字符串?dāng)?shù)組
                String[] factoryImplementationNames =
                    StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList變成
                        .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                          .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

這個方法執(zhí)行成功后,spring.factories 文件就已經(jīng)被加載進(jìn)來,并存放在 SpringFactoriesLoader 的緩存中

在 SpringBoot 啟動流程的后續(xù)步驟中,也會多次從這個緩存獲取相關(guān)數(shù)據(jù)

總結(jié)

以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 一文了解自定義MVC框架實(shí)現(xiàn)

    一文了解自定義MVC框架實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹一下MVC框架自定義實(shí)現(xiàn)過程,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定幫助,需要的可以參考一下
    2022-07-07
  • Springmvc模式上傳和下載與enctype對比

    Springmvc模式上傳和下載與enctype對比

    這篇文章主要介紹了Springmvc模式上傳和下載與enctype對比,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-12-12
  • Spring基于XML實(shí)現(xiàn)Aop

    Spring基于XML實(shí)現(xiàn)Aop

    這篇文章主要介紹了Spring中基于xml的AOP的詳細(xì)步驟,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-07-07
  • 基于Java中對域和靜態(tài)方法的訪問不具有多態(tài)性(實(shí)例講解)

    基于Java中對域和靜態(tài)方法的訪問不具有多態(tài)性(實(shí)例講解)

    下面小編就為大家?guī)硪黄贘ava中對域和靜態(tài)方法的訪問不具有多態(tài)性(實(shí)例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • 生產(chǎn)環(huán)境jvm常用的參數(shù)設(shè)置建議分享

    生產(chǎn)環(huán)境jvm常用的參數(shù)設(shè)置建議分享

    在Java應(yīng)用程序的部署過程中,合理配置JVM(Java虛擬機(jī))參數(shù)對于提升應(yīng)用性能、穩(wěn)定性和資源利用效率至關(guān)重要,本文將探討一些常用的JVM參數(shù)設(shè)置建議,幫助開發(fā)者在生產(chǎn)環(huán)境中優(yōu)化Java應(yīng)用,需要的朋友可以參考下
    2025-04-04
  • JDK的一個Bug監(jiān)聽文件變更的初步實(shí)現(xiàn)思路

    JDK的一個Bug監(jiān)聽文件變更的初步實(shí)現(xiàn)思路

    這篇文章主要介紹了JDK的一個Bug監(jiān)聽文件變更要小心了,本篇文章就帶大家簡單實(shí)現(xiàn)一個對應(yīng)的功能,并分析一下對應(yīng)的Bug和優(yōu)缺點(diǎn),需要的朋友可以參考下
    2022-05-05
  • Java實(shí)現(xiàn)把文件及文件夾壓縮成zip

    Java實(shí)現(xiàn)把文件及文件夾壓縮成zip

    這篇文章主要介紹了Java實(shí)現(xiàn)把文件及文件夾壓縮成zip,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • 徹底理解Java線程通信wait?/?notify(原理?+?實(shí)戰(zhàn))

    徹底理解Java線程通信wait?/?notify(原理?+?實(shí)戰(zhàn))

    在Java中,wait和notify是Object類的一部分,用于線程間的通信和同步,它們允許一個線程通知另一個線程某個事件的發(fā)生,或者請求釋放對象的控制權(quán),這篇文章主要介紹了Java線程通信wait/notify的相關(guān)資料,需要的朋友可以參考下
    2026-03-03
  • java生成驗(yàn)證碼圖片的方法

    java生成驗(yàn)證碼圖片的方法

    這篇文章主要為大家詳細(xì)介紹了java生成驗(yàn)證碼圖片的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-02-02
  • springboot配置kafka批量消費(fèi),并發(fā)消費(fèi)方式

    springboot配置kafka批量消費(fèi),并發(fā)消費(fèi)方式

    文章介紹了如何在Spring Boot中配置Kafka進(jìn)行批量消費(fèi),并發(fā)消費(fèi),需要注意的是,并發(fā)量必須小于等于分區(qū)數(shù),否則會導(dǎo)致線程空閑,文章還總結(jié)了創(chuàng)建Kafka分區(qū)的命令,并鼓勵讀者分享經(jīng)驗(yàn)
    2024-12-12

最新評論

兴宁市| 奉化市| 平遥县| 永德县| 安庆市| 建昌县| 兴城市| 富蕴县| 澄城县| 育儿| 古交市| 长白| 乌鲁木齐市| 陆良县| 廉江市| 祁门县| 定西市| 如东县| 广南县| 大庆市| 伽师县| 万山特区| 仪征市| 长治县| 南宁市| 玉田县| 炎陵县| 女性| 丰县| 丰都县| 施甸县| 西乌珠穆沁旗| 桐梓县| 佛山市| 共和县| 仙桃市| 金平| 鄂温| 息烽县| 六盘水市| 航空|