springboot啟動(dòng)流程1--啟動(dòng)前的準(zhǔn)備

  • 1 前言

本文是一個(gè)系列缔赠,整個(gè)啟動(dòng)流程比較長(zhǎng)涨醋,涉及到配置的初始化,環(huán)境的初始化郁季,以及bean的加載流程等冷溃。本系列將從分階段的逐步進(jìn)行解析钱磅。
文章基于springboot2.3.x系列的源碼,github的源碼與實(shí)際發(fā)版的可能略微不同似枕,不過(guò)整理流程差別不大盖淡。
本人第一次寫文章,如有錯(cuò)誤或誤導(dǎo)歡迎留言指正凿歼。文章整體可能比較啰嗦褪迟,盡可能的將流程中的每一個(gè)重要的方法都講到。

  • 2 SpringApplication初始化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //基礎(chǔ)資源啟動(dòng)類答憔,默認(rèn)為當(dāng)前啟動(dòng)類
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //判斷web服務(wù)類型牵咙,現(xiàn)在不是推出了reactive web嘛 區(qū)別一下
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //獲取ApplicationContextInitializer子類包括自定義的類,用于在applicationContext容器刷新之前調(diào)用
    //在prepareContext方法中通過(guò)applyInitializers對(duì)上下文加載器進(jìn)行定制化的操作
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //從堆棧信息中推斷當(dāng)前啟動(dòng)的類
        //感興趣的也可以了解一下
        this.mainApplicationClass = deduceMainApplicationClass();
}

getSpringFactoriesInstances攀唯,從方法的名稱可以大概知道是獲取ApplicationContextInitializer(應(yīng)用上下文初始化)和 ApplicationListener 實(shí)例工廠洁桌,根據(jù)傳入類的類型。

 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

getSpringFactoriesInstances的內(nèi)部又引出了兩個(gè)方法侯嘀,loadFactoryNames和createSpringFactoriesInstances另凌。
下面逐個(gè)進(jìn)行分析。

loadFactoryNames:

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        // 這里使用Map的getOrDefault方法 平時(shí)很少使用戒幔,將默認(rèn)值提前設(shè)置進(jìn)去吠谢,在map中取不到結(jié)果的時(shí)候返回默認(rèn)值。
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
     private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 優(yōu)先從緩存中獲取
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    // 通過(guò)classLoader獲取jar中的spring.factories文件路徑
    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    // 以下為主要代碼
    // 循環(huán)遍歷url結(jié)果
    UrlResource resource = new UrlResource(url);
    // 通過(guò)路徑加載配置诗茎, 將配置內(nèi)的屬性轉(zhuǎn)換成Map的形式
    // Properties為HashMap的子類
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);

    // 遍歷map 對(duì)value串通過(guò)“,”截取成list
    // 遍歷list將其存入LinkedMultiValueMap中 注意這里的LinkedMultiValueMap為MultiValueMap子類 其頂層依然是一個(gè)
    // map不過(guò)內(nèi)部的value是一個(gè)list
    LinkedMultiValueMap result = new LinkedMultiValueMap();
    // 緩存類加載器作為key 緩存結(jié)果工坊,減少后續(xù)加載
    cache.put(classLoader, result);
    return result;
}

實(shí)現(xiàn)過(guò)或看過(guò)springboot的start包的小伙伴應(yīng)該豁然開(kāi)朗了,在start類的jar包下會(huì)有個(gè)spring.factories文件,記錄著配置類。
那也就是在這里對(duì)這些文件進(jìn)行了掃描加載并將路徑存放到緩存內(nèi)重抖。
另外,從這里也能看到其實(shí)spring用了很多java底層的東西昭齐,比如這里的classloader.getSystemResources,自定義Map類等矾柜。經(jīng)弛寮荩看源碼可能了解到很多沒(méi)用過(guò)的騷操作。

這里留一個(gè)問(wèn)題怪蔑,為何源碼中將classloader作為參數(shù)層層傳遞下去里覆,而不是直接在當(dāng)前類中使用this.getClassloader?

到此只是找到了這些需要加載的類,但是還未加載缆瓣。

createSpringFactoriesInstances:

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    // 獲取class對(duì)象喧枷,這個(gè)classUtils感興趣的也可以了解一下 
    // 其內(nèi)部在啟動(dòng)時(shí)候初始化了一部分java基礎(chǔ)的提升加載速度
    Class<?> instanceClass = ClassUtils.forName(name, classLoader);
    Assert.isAssignable(type, instanceClass);
    //獲取一個(gè)指定參數(shù)類型的構(gòu)造函數(shù)
    Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
    // 通過(guò)構(gòu)造函數(shù)和參數(shù) 獲取一個(gè)實(shí)例
    // instantiateClass 方法感興趣的也可以去了解一下,內(nèi)部使用了spring自身的反射工具類,這里能了解到更多的反射應(yīng)用
    // 而不僅僅是class.forname 有空的話這些工具類的方法和使用也會(huì)做一下割去,這里面才有很多java底層的應(yīng)用窟却。而且相對(duì)很規(guī)范
    T instance = BeanUtils.instantiateClass(constructor, args);
}

到此為止昼丑,從spring.factories文件中加載了全部的配置呻逆,并且初始化了與ApplicationContextInitializer(應(yīng)用上下文初始化)ApplicationListener相關(guān)的實(shí)例工廠。至于它們的作用菩帝,我們繼續(xù)向下看咖城。

這里強(qiáng)調(diào)一下,這里是實(shí)例化相當(dāng)于java的new關(guān)鍵字呼奢,且并沒(méi)有放入spring的bean容器中宜雀。

  • 3 run方法執(zhí)行

上run方法源碼前,這里在提一下握础,不同的版本辐董,加載上下文(即初始化bean)前后的方法可能不太相同,但一般影響不大禀综。

源碼:

//基礎(chǔ)的run方法
public ConfigurableApplicationContext run(String... args) {
    //spring的計(jì)時(shí)器  以后可以用這個(gè)實(shí)現(xiàn)简烘,可記錄多個(gè)任務(wù)時(shí)間
    StopWatch stopWatch = new StopWatch();
    //開(kāi)啟計(jì)時(shí)器
    stopWatch.start();
    //初始化上下文應(yīng)用
    ConfigurableApplicationContext context = null;
    //初始化異常報(bào)告集合
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    //配置系統(tǒng)參數(shù) java.awt.headless ,簡(jiǎn)單的來(lái)說(shuō)就是告訴服務(wù)需要在沒(méi)有外設(shè)和一些硬件的方式下運(yùn)行,和圖形處理有關(guān)定枷。
    this.configureHeadlessProperty();
    //監(jiān)聽(tīng)器集合發(fā)布事件孤澎,注意這個(gè)是一個(gè)異步事件
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //在run()方法開(kāi)始執(zhí)行時(shí),該方法就立即被調(diào)用欠窒,可用于在初始化最早期時(shí)做一些工作
    //SpringApplicationRunListener中的每個(gè)方法都有調(diào)用的階段覆旭。
    listeners.starting();
    Collection exceptionReporters;
    try {
        //初始化應(yīng)用默認(rèn)參數(shù)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //初始化準(zhǔn)備環(huán)境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //創(chuàng)建打印類
        Banner printedBanner = this.printBanner(environment);
        //創(chuàng)建應(yīng)用上下文
        context = this.createApplicationContext();
        //準(zhǔn)備異常報(bào)告器
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
        //準(zhǔn)備應(yīng)用上下文
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        //刷新應(yīng)用上下文
        this.refreshContext(context);
        //上下文后置處理
        this.afterRefresh(context, applicationArguments);
        //計(jì)時(shí)器結(jié)束
        stopWatch.stop();
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }
        //應(yīng)用上下文啟動(dòng)后執(zhí)行監(jiān)聽(tīng)器
        listeners.started(context);
        //執(zhí)行所有的runner,CommandLineRunner&ApplicationRunner
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        //異常處理
        this.handleRunFailure(context, var10, exceptionReporters, listeners);
        throw new IllegalStateException(var10);
    }
    try {
        //應(yīng)用上下文就緒事件
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

run包含了整個(gè)啟動(dòng)的流程岖妄,本節(jié)只講解到banner的打印型将,并簡(jiǎn)單的說(shuō)一下如何個(gè)性化的定制自己專屬的啟動(dòng)圖文,雖然沒(méi)有卵用荐虐,但是快樂(lè)呀茶敏!

選出本節(jié)關(guān)注的內(nèi)容:

 //spring的計(jì)時(shí)器  以后可以用這個(gè)實(shí)現(xiàn),可記錄多個(gè)任務(wù)時(shí)間
    StopWatch stopWatch = new StopWatch();
    //開(kāi)啟計(jì)時(shí)器
    stopWatch.start();
    //初始化上下文應(yīng)用
    ConfigurableApplicationContext context = null;
    //初始化異常報(bào)告集合
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    //配置系統(tǒng)參數(shù) java.awt.headless ,簡(jiǎn)單的來(lái)說(shuō)就是告訴服務(wù)需要在沒(méi)有外設(shè)和一些硬件的方式下運(yùn)行缚俏,和圖形處理有關(guān)惊搏。
    this.configureHeadlessProperty();
    //監(jiān)聽(tīng)器集合發(fā)布事件,注意這個(gè)是一個(gè)異步事件
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //在run()方法開(kāi)始執(zhí)行時(shí)忧换,該方法就立即被調(diào)用恬惯,可用于在初始化最早期時(shí)做一些工作
    //SpringApplicationRunListener中的每個(gè)方法都有調(diào)用的階段。
    listeners.starting();
    Collection exceptionReporters;

        //初始化應(yīng)用默認(rèn)參數(shù)
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //初始化準(zhǔn)備環(huán)境
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //創(chuàng)建打印類
        Banner printedBanner = this.printBanner(environment);

從計(jì)時(shí)器StopWatch開(kāi)始亚茬,想想spring框架的貢獻(xiàn)者是什么樣的大佬酪耳,怎么可會(huì)直接使用System.currentTimeMillis()來(lái)計(jì)算呢!這個(gè)原理并不復(fù)雜,內(nèi)部使用的也是System.nanoTime精度更高的納秒碗暗,而且在很多開(kāi)源的工具包中都有提供颈将,比如我們常用的Apache的commons,hutool等言疗,但內(nèi)部的方法并不完全相同晴圾,spring提供的更像跑步比賽時(shí)的計(jì)時(shí)器,可以停止多次噪奄。

configureHeadlessProperty :設(shè)置java.awt.headless屬性死姚,告訴java應(yīng)用程序,當(dāng)前運(yùn)行環(huán)境缺少鍵盤勤篮、鼠標(biāo)都毒、顯示器等設(shè)備,你得自己堅(jiān)強(qiáng)碰缔。

監(jiān)聽(tīng)器:

 SpringApplicationRunListeners listeners = this.getRunListeners(args);
 listeners.starting();

這里就知道在初始化階段账劲,獲取監(jiān)聽(tīng)器是做啥用的了,記住這里的監(jiān)聽(tīng)器金抡,是為了整個(gè)啟動(dòng)流程服務(wù)的瀑焦。不是我們?nèi)粘V凶远x的用來(lái)處理異步事件的監(jiān)聽(tīng)器,它們實(shí)現(xiàn)的接口是不同的竟终,這里實(shí)現(xiàn)的是SpringApplicationRunListener 從名稱就很容易看出蝠猬,是為了上下文啟動(dòng)使用的,而我們常用的是 ApplicationListener
统捶。另外特別強(qiáng)調(diào)一下starting不能理解為啟動(dòng)監(jiān)聽(tīng)器榆芦,而是執(zhí)行與啟動(dòng)前準(zhǔn)備工作相關(guān)的監(jiān)聽(tīng)器。
這點(diǎn)通過(guò)源碼可以很容易理解:

public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment environment);

    void contextPrepared(ConfigurableApplicationContext context);

    void contextLoaded(ConfigurableApplicationContext context);

    void started(ConfigurableApplicationContext context);

    void running(ConfigurableApplicationContext context);

    void failed(ConfigurableApplicationContext context, Throwable exception);
}

除了starting之外還有environmentPrepared喘鸟、contextPrepared匆绣、contextLoaded、started什黑、runing等崎淳。對(duì)應(yīng)啟動(dòng)流程的各個(gè)階段。

關(guān)于監(jiān)聽(tīng)器內(nèi)部的原理這里也不展開(kāi)說(shuō)了愕把,單拿出來(lái)還能水一篇哈哈拣凹!挖坑挖坑!

因此在自定義start啟動(dòng)jar的時(shí)候恨豁,若要使用SpringApplicationRunListener嚣镜,則需要明確自己所想應(yīng)用的階段。

到這為止橘蜜,還都算很簡(jiǎn)單菊匿。下面開(kāi)始稍微有那么一內(nèi)內(nèi)復(fù)雜的。默認(rèn)參數(shù)初始化,雖然我們?cè)谑褂胷un方法的時(shí)候幾乎不會(huì)傳入?yún)?shù)跌捆,但人家支持呀徽职!那就要了解一下!

 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

ApplicationArguments 對(duì)象本身沒(méi)有什么好說(shuō)的佩厚,提供應(yīng)用上下文的參數(shù)初始化姆钉。但它有一個(gè)內(nèi)部類

 private static class Source extends SimpleCommandLinePropertySource {
    ...
    ...
}

看到這個(gè)父類的名稱,應(yīng)該就能猜到這個(gè)與PropertySource有關(guān)可款,那我們?cè)诖_認(rèn)一下類的關(guān)系圖育韩。

未命名圖片.png

關(guān)系圖上我們可以大致了解到克蚂,這里可以接受run方法傳入的參數(shù)作為comandline的命令參數(shù)闺鲸,這里將參數(shù)封裝成PropertySource對(duì)象并放入 ApplicationArguments 中以備使用。
參數(shù)最終都是通過(guò)構(gòu)造函數(shù)super關(guān)鍵字傳遞到頂層的PropertySource中埃叭。

關(guān)于SimpleCommandLinePropertySource 這個(gè)就不展開(kāi)說(shuō)了摸恍,這里的確是命令行參數(shù)設(shè)置,例如: --name=zhangsan,另外這里的設(shè)置可以通過(guò)@Value獲取到赤屋,這屬于騷操作的范圍了吧立镶,很少這么使用。這里可以替換配置文件中的屬性类早,比如在application.properties中的server.port屬性可以在這里通過(guò)參數(shù)--server.port的替換,本質(zhì)上它是作為啟動(dòng)參數(shù)來(lái)執(zhí)行的媚媒,相當(dāng)于 java -jar --server.port=8080 優(yōu)先級(jí)高于配置文件。
入?yún)⒌母袷揭残枰⒁猓?-開(kāi)頭和非 --開(kāi)頭的解析方式不同這里需要格外注意涩僻。

    public PropertySource(String name, T source) {
        this.logger = LogFactory.getLog(this.getClass());
        Assert.hasText(name, "Property source name must contain at least one character");
        Assert.notNull(source, "Property source must not be null");
        this.name = name;
        this.source = source;
    }

PropertySource 也是一個(gè)spring中比較重要的類缭召,它是頂層的配置資源封裝類,在很多地方都有應(yīng)用逆日,與BeanDefinition一樣嵌巷,作為spring的規(guī)范應(yīng)用的一種,對(duì)擴(kuò)展使用很方便室抽。

prepareEnvironment : 準(zhǔn)備啟動(dòng)環(huán)境
源碼:

    ConfigurableEnvironment environment = this.prepareEnvironment(listeners,applicationArguments);
    this.configureIgnoreBeanInfo(environment);
    Banner printedBanner = this.printBanner(environment);

這里有兩個(gè)部分:初始化環(huán)境配置和打印banner

初始化環(huán)境配置源碼:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    //根據(jù)web應(yīng)用類型獲取環(huán)境
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    //配置環(huán)境參數(shù)和profiles.active信息
    this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
    
    ConfigurationPropertySources.attach((Environment)environment);
    listeners.environmentPrepared((ConfigurableEnvironment)environment);
    this.bindToSpringApplication((ConfigurableEnvironment)environment);
    if (!this.isCustomEnvironment) {
        environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach((Environment)environment);
    return (ConfigurableEnvironment)environment;
}

老樣子搪哪,逐個(gè)分析。
getOrCreateEnvironment:獲取環(huán)境類

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        } else {
            switch(this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
            }
        }
    }

webApplicationType: 參數(shù)在SpringApplication構(gòu)造函數(shù)內(nèi)(部分源碼可能是創(chuàng)建時(shí)就直接初始化了)初始化的坪圾。

這里返回的結(jié)果一般都是StandardServletEnvironment 目前我們未使用響應(yīng)式web框架晓折,即spring 5開(kāi)始推出的webflux.這點(diǎn)以后也需要學(xué)習(xí)一下。

StandardServletEnvironment的父類AbstractEnvironment的構(gòu)造函數(shù)中也進(jìn)行了配置資源MutablePropertySources(環(huán)境配置資源保存)和propertyResolver(資源解析)的初始化兽泄,獲取了系統(tǒng)配置和環(huán)境變量等配置信息漓概,后面用到了再說(shuō)再細(xì)說(shuō)。

configureEnvironment:配置環(huán)境

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService)conversionService);
        }

        this.configurePropertySources(environment, args);
        this.configureProfiles(environment, args);
    }

ConversionService:為環(huán)境設(shè)置轉(zhuǎn)換服務(wù)已日,這里配置參數(shù)addConversionService默認(rèn)為true垛耳,因此這里必定會(huì)初始化。

ApplicationConversionService.getSharedInstance()也并不復(fù)雜,從名稱可以知道為上下文轉(zhuǎn)換服務(wù)堂鲜,這里 getSharedInstance 通過(guò)懶漢式的方式獲取了一個(gè)ApplicationConversionService的單例栈雳。

在構(gòu)造函數(shù)內(nèi)初始化了一些轉(zhuǎn)換和格式化服務(wù)。比如String轉(zhuǎn)number缔莲,Date轉(zhuǎn)String等哥纫。也是配置文件讀取時(shí),屬性轉(zhuǎn)到配置類中或@value注解綁定參數(shù)等地方使用痴奏。

擴(kuò)展說(shuō)一下單例模式的實(shí)現(xiàn)蛀骇,餓漢式和懶漢式,正常的情況下 懶漢式是非線程安全的读拆。而餓漢式是天生的線程安全擅憔。因此若使用懶漢式,需要解決線程安全問(wèn)題檐晕。這里getSharedInstance示范了一個(gè)標(biāo)準(zhǔn)的線程安全懶漢式模式之一暑诸,比如還可以通過(guò)枚舉類實(shí)現(xiàn)。

下面我們看一下getSharedInstance源碼:

    public static ConversionService getSharedInstance() {
        ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
        if (sharedInstance == null) {
            Class var1 = ApplicationConversionService.class;
            synchronized(ApplicationConversionService.class) {
                sharedInstance = ApplicationConversionService.sharedInstance;
                if (sharedInstance == null) {
                    sharedInstance = new ApplicationConversionService();
                    ApplicationConversionService.sharedInstance = sharedInstance;
                }
            }
        }

        return sharedInstance;
    }

這個(gè)扯遠(yuǎn)了辟灰,但是真心覺(jué)得源碼中个榕,代碼的編寫和一些java原生工具的使用都非常好,很多細(xì)小的點(diǎn)都值得學(xué)習(xí)芥喇。不僅僅只為了弄清一個(gè)spring的啟動(dòng)流程西采,更多的要看大佬們是如何編寫代碼的。

繼續(xù)继控,獲取轉(zhuǎn)換服務(wù)后設(shè)置到環(huán)境對(duì)象中以備使用械馆。

configurePropertySources 配置資源處理。

 protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        // 這個(gè)屬性在AbstractEnvironment的構(gòu)造函數(shù)中初始化
        MutablePropertySources sources = environment.getPropertySources();
        //這里defaultProperties 默認(rèn)為空
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        // 前面說(shuō)到的commandline 配置參數(shù)
        if (this.addCommandLineProperties && args.length > 0) {
            String name = "commandLineArgs";
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }

    }

這里將傳入的配置資源以及默認(rèn)的配置資源存入環(huán)境的資源中湿诊。

順便吐槽一下狱杰,原來(lái)spring中也有很多寫死的常量屬性,雖大部分情況下這種屬性相對(duì)不會(huì)改變厅须。但個(gè)人還是有點(diǎn)想吐槽仿畸!

ConfigurationPropertySources.attach:設(shè)置:configurationProperties配置屬性

    public static void attach(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
        PropertySource<?> attached = sources.get("configurationProperties");
        if (attached != null && attached.getSource() != sources) {
            sources.remove("configurationProperties");
            attached = null;
        }

        if (attached == null) {
            sources.addFirst(new ConfigurationPropertySourcesPropertySource("configurationProperties", new SpringConfigurationPropertySources(sources)));
        }

    }

校驗(yàn)原環(huán)境配置屬性列表中是否包含 configurationProperties 若不包含則新增進(jìn)去。存在的話對(duì)比是否是同一個(gè)對(duì)象朗和,若不是則刪除重新添加错沽。這里比較有意思的是,sources是從環(huán)境對(duì)象中獲取的眶拉,然后將獲取的結(jié)果通過(guò)SpringConfigurationPropertySources封裝千埃,并作為sources的一個(gè)新的configurationProperties屬性放入sources中.

到目前為止還不能明確這么做的目的。帶著疑問(wèn)繼續(xù)向下看忆植。

listeners.environmentPrepared:這個(gè)就不多說(shuō)了放可,環(huán)境已經(jīng)準(zhǔn)備好執(zhí)行與此相關(guān)的監(jiān)聽(tīng)器谒臼。

bindToSpringApplication

這個(gè)就比較有意思了,從名字理解耀里,為SpringApplication綁定蜈缤,綁定什么呢?

    protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception var3) {
            throw new IllegalStateException("Cannot bind to SpringApplication", var3);
        }
    }

為了方便理解binder的原理冯挎,我們?cè)诜乓幌耣inder.get的源碼:

    public static Binder get(Environment environment, BindHandler defaultBindHandler) {
        Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
        PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);
        return new Binder(sources, placeholdersResolver, (ConversionService)null, (Consumer)null, defaultBindHandler);
    }

從這里可以看到一個(gè)ConfigurationPropertySources.get方法底哥,從環(huán)境中獲取了ConfigurationPropertySource的配置資源迭代器。

其源碼:

    public static Iterable<ConfigurationPropertySource> get(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
        ConfigurationPropertySourcesPropertySource attached = (ConfigurationPropertySourcesPropertySource)sources.get("configurationProperties");
        return attached == null ? from((Iterable)sources) : (Iterable)attached.getSource();
    }

這里知道了前文為何又將配置資源設(shè)置到configurationProperties中房官,在這里通過(guò)可配置環(huán)境類ConfigurableEnvironment獲取環(huán)境中的指定的配置資源趾徽。
這里最后返回的結(jié)果是Iterable類型,因?yàn)閏onfigurationProperties內(nèi)保存的是一個(gè)列表翰守。

我們?cè)倩氐紹inder.get的源碼中孵奶。除了獲取環(huán)境類中的配置資源,還初始化了一個(gè)配置資源占位符解析器

  PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);

PropertySourcesPlaceholdersResolver:

    public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
        this.sources = sources;
        this.helper = helper != null ? helper : new PropertyPlaceholderHelper("${", "}", ":", true);
    }

這樣就比較直觀潦俺,這里是要替換掉配置文件中的${xx}占位符拒课。

至于bind方法這里不打算展開(kāi)說(shuō)了徐勃,知道是將spring.main下面的配置綁定到SpringApplication對(duì)象上事示。如:sources,bannerMode等屬性賦值給當(dāng)前的對(duì)象僻肖。

這里額外提一點(diǎn)肖爵,從類的名稱區(qū)猜測(cè)類的作用在spring源碼中真的非常好用,比如上面源碼中的ConfigurableEnvironment臀脏,從名稱可猜測(cè)為可配置的環(huán)境劝堪,他的實(shí)現(xiàn)類之一就是我們上面說(shuō)的StandardServletEnvironment,spring將環(huán)境中可配置屬性的方式揉稚,提到接口中秒啦。既方便擴(kuò)展,也直觀的展示了那些屬性是可以通過(guò)配置修改的搀玖。

判斷是否需要轉(zhuǎn)換環(huán)境對(duì)象

        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }

isCustomEnvironment參數(shù)默認(rèn)為false余境,這里必定會(huì)執(zhí)行。內(nèi)部對(duì)比了當(dāng)前環(huán)境對(duì)象是否為StandardEnvironment子類灌诅。若不是則重新生成StandardEnvironment環(huán)境對(duì)象芳来,并將內(nèi)部的參數(shù)轉(zhuǎn)換到新的對(duì)象中

方法結(jié)尾,重新執(zhí)行了ConfigurationPropertySources.attach方法猜拾。因?yàn)榍拔目赡芤呀?jīng)對(duì)資源或環(huán)境類進(jìn)行樂(lè)修改即舌。這里相當(dāng)于重新初始化了一次環(huán)境配置資源類中的configurationProperties的配置。

到此為止挎袜,我們講完了prepareEnvironment(環(huán)境對(duì)象初始化流程)顽聂。

configureIgnoreBeanInfo :設(shè)置需要被忽略的beaninfo肥惭。

最后介紹一下:printBanner 啟動(dòng)banner的打印。

源碼:

    private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Mode.OFF) {
            return null;
        } else {
            ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader((ClassLoader)null);
            SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
            return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);
        }
    }

this.bannerMode在SpringApplication構(gòu)造函數(shù)內(nèi)初始化(部分版本源碼是創(chuàng)建時(shí)初始化)紊搪。默認(rèn)為“CONSOLE”务豺。
上面的源碼比較簡(jiǎn)單,提供了兩種打印方式嗦明,并初始化一個(gè)banner對(duì)象笼沥。
bannerPrinter.print內(nèi)提供了兩種不同的banner類型。

    private Banner getBanner(Environment environment) {
        SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();
        banners.addIfNotNull(this.getImageBanner(environment));
        banners.addIfNotNull(this.getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        } else {
            return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
        }
    }

getImageBanner和getTextBanner娶牌,圖片和文本兩種模式奔浅。
這里還未真正打印,只是初始化好了一個(gè)banner對(duì)象诗良。

    private Banner getTextBanner(Environment environment) {
        // spring.banner.location 屬性汹桦, 默認(rèn)為 banner.txt
        String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
        Resource resource = this.resourceLoader.getResource(location);
        try {
            if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
                return new ResourceBanner(resource);
            }
        }
        catch (IOException ex) {
            // Ignore
        }
        return null;
    }

    private Banner getImageBanner(Environment environment) {
        //spring.banner.location 需要指定文件
        String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
        if (StringUtils.hasLength(location)) {
            Resource resource = this.resourceLoader.getResource(location);
            return resource.exists() ? new ImageBanner(resource) : null;
        }
        for (String ext : IMAGE_EXTENSION) {
            Resource resource = this.resourceLoader.getResource("banner." + ext);
            if (resource.exists()) {
                return new ImageBanner(resource);
            }
        }
        return null;
    }

通過(guò)配置可以看出在resource下默認(rèn)創(chuàng)建一個(gè)banner.txt文本即可替換原始的banner信息。這種方式比較簡(jiǎn)單鉴裹。

  • 4 小結(jié)

本文講解了從啟動(dòng)到創(chuàng)建上下文之前的動(dòng)作舞骆,包括SpringApplication初始化(加載spring.factories文件中的配置類,監(jiān)聽(tīng)器等)径荔,運(yùn)行環(huán)境的初始化(環(huán)境類以及內(nèi)部的配置資源的初始化)督禽,banner對(duì)象的構(gòu)建等。

到此為止都算比較簡(jiǎn)單总处,這里重點(diǎn)關(guān)注一下環(huán)境類(Environment)后續(xù)使用的會(huì)比較多狈惫,配置資源是從這里獲取,提供了springboot運(yùn)行時(shí)環(huán)境鹦马。

文章講的比較啰嗦胧谈,很多點(diǎn)沒(méi)有詳細(xì)深入,如果詳細(xì)的說(shuō)荸频,一篇文章根本說(shuō)不完菱肖,比如監(jiān)聽(tīng)器以及一些工具類等。

簡(jiǎn)單的流程:
1.初始化SpringApplication構(gòu)造函數(shù)
2.從spring.factories文件中加載配置類
3.加載配置資源(系統(tǒng)配置旭从,環(huán)境變量稳强,項(xiàng)目的application.yml配置等)
4.初始化環(huán)境參數(shù),將配置資源注入
5.構(gòu)建banner對(duì)象

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遇绞,一起剝皮案震驚了整個(gè)濱河市键袱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摹闽,老刑警劉巖蹄咖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異付鹿,居然都是意外死亡澜汤,警方通過(guò)查閱死者的電腦和手機(jī)蚜迅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俊抵,“玉大人谁不,你說(shuō)我怎么就攤上這事』栈澹” “怎么了刹帕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谎替。 經(jīng)常有香客問(wèn)我偷溺,道長(zhǎng),這世上最難降的妖魔是什么钱贯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任挫掏,我火速辦了婚禮,結(jié)果婚禮上秩命,老公的妹妹穿的比我還像新娘尉共。我一直安慰自己,他們只是感情好弃锐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布袄友。 她就那樣靜靜地躺著,像睡著了一般拿愧。 火紅的嫁衣襯著肌膚如雪杠河。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,785評(píng)論 1 290
  • 那天浇辜,我揣著相機(jī)與錄音,去河邊找鬼唾戚。 笑死柳洋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叹坦。 我是一名探鬼主播熊镣,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼募书!你這毒婦竟也來(lái)了绪囱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤莹捡,失蹤者是張志新(化名)和其女友劉穎鬼吵,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體篮赢,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡齿椅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年琉挖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涣脚。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡示辈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遣蚀,到底是詐尸還是另有隱情矾麻,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布芭梯,位于F島的核電站射富,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏粥帚。R本人自食惡果不足惜胰耗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芒涡。 院中可真熱鬧柴灯,春花似錦、人聲如沸费尽。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)旱幼。三九已至查描,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柏卤,已是汗流浹背冬三。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缘缚,地道東北人勾笆。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像桥滨,于是被迫代替她去往敵國(guó)和親窝爪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

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