?? Spring 擴展點應(yīng)用之工廠加載機制

Spring 工廠加載機制鸟蟹,即 Spring Factories Loader蜕提,核心邏輯是使用 SpringFactoriesLoader 加載由用戶實現(xiàn)的類权旷,并配置在約定好的META-INF/spring.factories 路徑下,該機制可以為框架上下文動態(tài)的增加擴展贯溅。
該機制類似于 Java SPI,給用戶提供可擴展的鉤子躲查,從而達到對框架的自定義擴展功能它浅。

核心實現(xiàn)類 SpringFactoriesLoader

SpringFactoriesLoaderSpring 工廠加載機制的核心底層實現(xiàn)類。它的主要作用是 從 META-INF/spring.factories 路徑下加載指定接口的實現(xiàn)類镣煮。該文件可能存在于工程類路徑下或者 jar 包之內(nèi)姐霍,所以會存在多個該文件。

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

SpringFactoriesLoader loadFactories load 并從 FACTORIES_RESOURCE_LOCATION文件中實例化給定類型的工廠實現(xiàn)類。 spring.factories 文件必須采用 Properties 格式镊折,其中鍵是接口或抽象類的完全限定*名稱胯府,值是逗號分隔的實現(xiàn)類名稱列表。例如:

該文件的格式恨胚,Key 必須為接口或抽象類的全限定名骂因,value 為 具體的實現(xiàn)類,多個以 逗號分隔赃泡。類似如下配置:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

從該文件中我們可以看到寒波,其中 ApplicationContextInitializer 為父類,value為實現(xiàn)類升熊,以逗號分隔俄烁。

SpringFactoriesLoader 源碼分析

Spring Boot 完成自動裝配的核心之一就是工廠加載機制。我們以 Spring Boot 的自動裝配為例來分析级野。如果要開啟 Spring 的自動裝配功能页屠,會使用 @EnableAutoConfiguration 這個注解,這個注解會 Import AutoConfigurationImportSelector 這個類蓖柔。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
AutoConfigurationImportSelector 中有一個方法就是加載 EnableAutoConfigurationkey 的實現(xiàn)配置類辰企。
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
    return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader)
}
SpringFactoriesLoader loadFactories 加載 所有以 factoryClassKey 的實現(xiàn)類
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        // 省略部分前置判斷和 logger 代碼 
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        //根據(jù)當前接口類的全限定名作為key,從loadFactoryNames從文件中獲取到所有實現(xiàn)類的全限定名
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        
        List<T> result = new ArrayList<>(factoryNames.size());
        //實例化所有實現(xiàn)類渊抽,并保存到 result 中返回蟆豫。
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }
調(diào)用 loadSpringFactoriesMETA-INF/spring.factories文件中進行加載

從文件中讀取接口和實現(xiàn)類的邏輯,返回 Map<String, List<String>>

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }
        try {
            //FACTORIES_RESOURCE_LOCATION --> META-INF/spring.factories
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            //一Key多值 Map懒闷,適合上文提到的一個接口多個實現(xiàn)類的情形十减。
            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()) {
                    //以逗號進行分割,得到List的實現(xiàn)類全限定名集合
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            //返回
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
}

總結(jié)

上面通過以 Spring Boot 的自動裝配為例愤估,我們分析了 Spring 工廠加載機制的整個過程帮辟,重點分析了SpringFactoriesLoader類。通過這樣的機制玩焰,我們可以十分的方便的為框架提供各式各樣的擴展插件由驹,我們可以自己定義自己的組件的自動裝配配置類,然后通過工廠加載機制讓 Spring 進行加載并得到自動裝配昔园。

工廠加載機制的應(yīng)用 ApplicationContextInitializer

ApplicationContextInitializer 是在 Spring Boot 或者 Spring Mvc 啟動過程中調(diào)用的蔓榄。具體時機為Spring 應(yīng)用上下文 refresh 之前(調(diào)用 refresh 方法)。

ApplicationContextInitializer 主要提供應(yīng)用上下文未refresh之前的擴展默刚,這時候可以對 ConfigurableApplicationContext 進行一些擴展處理等甥郑。

自定義一個類,實現(xiàn) ApplicationContextInitializer 荤西,并重寫 initialize 方法:

@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("=================> applicationContext: " + applicationContext.getId());
    }
}

啟動 Spring Boot 程序澜搅,我們可以看到在 refresh 之前伍俘,會在控制臺打印上面這句話。

  • 另外的實現(xiàn)方式:
    • application.properties添加配置方式:
context.initializer.classes=com.maple.spring.initializer.AfterHelloWorldApplicationContextInitializer

對于這種方式是通過 DelegatingApplicationContextInitializer 這個初始化類中的 initialize 方法獲取到 application.propertiescontext.initializer.classes 對應(yīng)的實現(xiàn)類勉躺,并對該實現(xiàn)類進行加載癌瘾。

3.在 SpringApplication 中直接添加

public static void main(String[] args) {
        new SpringApplicationBuilder(SpringBootDemo3Application.class)
                .initializers(new AfterHelloWorldApplicationContextInitializer())
                .run(args);
    }
}

Spring Boot 使用 工廠機制加載 ApplicationListener 實現(xiàn)類

# Application Listeners
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.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

總結(jié)

工廠加載機制是 Spring 動態(tài)加載實現(xiàn)類的一種方式,提前在擴展類中寫好對應(yīng)自動配置類饵溅,我們可以完成自動裝配的效果妨退。Spring Boot 自動裝配模塊其中的loader 自動配置實現(xiàn)類就是基于此實現(xiàn)的。
Spring Boot 的一些新特性幾乎用到的都是 Spring Framework 的核心特性概说。因此學(xué)習 Spring Boot 碧注,歸根結(jié)底就是學(xué)習 Spring Framework 核心。它是所有 Spring 應(yīng)用的基石糖赔,所以我們應(yīng)該從上至下萍丐,由淺入深來進行學(xué)習和分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末放典,一起剝皮案震驚了整個濱河市逝变,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奋构,老刑警劉巖壳影,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弥臼,居然都是意外死亡宴咧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門径缅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掺栅,“玉大人,你說我怎么就攤上這事纳猪⊙跷裕” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵氏堤,是天一觀的道長沙绝。 經(jīng)常有香客問我,道長鼠锈,這世上最難降的妖魔是什么闪檬? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮购笆,結(jié)果婚禮上谬以,老公的妹妹穿的比我還像新娘。我一直安慰自己由桌,他們只是感情好,可當我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著行您,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烈疚,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天窖剑,我揣著相機與錄音,去河邊找鬼捌斧。 笑死笛质,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的捞蚂。 我是一名探鬼主播妇押,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姓迅!你這毒婦竟也來了敲霍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤丁存,失蹤者是張志新(化名)和其女友劉穎肩杈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體解寝,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡扩然,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了聋伦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夫偶。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嘉抓,靈堂內(nèi)的尸體忽然破棺而出索守,到底是詐尸還是另有隱情,我是刑警寧澤抑片,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布卵佛,位于F島的核電站,受9級特大地震影響敞斋,放射性物質(zhì)發(fā)生泄漏截汪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一植捎、第九天 我趴在偏房一處隱蔽的房頂上張望衙解。 院中可真熱鬧,春花似錦焰枢、人聲如沸蚓峦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽暑椰。三九已至霍转,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間一汽,已是汗流浹背避消。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留召夹,地道東北人岩喷。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像监憎,于是被迫代替她去往敵國和親纱意。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容