SpringBoot源碼解析 -- SpringBoot啟動過程

SpringBoot深入理解 -- @AliasFor注解的作用
SpringBoot源碼解析 -- SpringBoot啟動過程
SpringBoot源碼解析 -- AutoConfigure的實現(xiàn)原理
SpringBoot源碼解析 -- @ComponentScan的實現(xiàn)原理
SpringBoot源碼解析 -- @Value,@Autowired實現(xiàn)原理
SpringBoot源碼解析 -- Tomcat诊胞,SpringMVC啟動
SpringBoot源碼解析 -- Logging怎爵,Environment啟動

源碼分析基于spring boot 2.1

本文通過閱讀源碼栏饮,分析SpringBoot的啟動過程局服。

先看一個例子

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);   // #1
    }
}

例子很簡單,本文主要關(guān)注三個問題

  1. SpringApplication#run方法的作用
  2. SpringApplication#run方法中MyApplication.class參數(shù)的作用
  3. SpringApplication#run方法中args參數(shù)的作用

SpringApplication#run

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

先構(gòu)造SpringApplication實例浊竟,再調(diào)用run方法

SpringApplication#構(gòu)造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;

    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));   // #1
    this.webApplicationType = WebApplicationType.deduceFromClasspath(); //#2
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //#3
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //#4
    this.mainApplicationClass = deduceMainApplicationClass();   // #5
}

#1 設(shè)置SpringApplication#primarySources怨喘,注意這里primarySources參數(shù)就是run方法的第一個參數(shù)
#2 判斷當(dāng)前應(yīng)用是JAVA應(yīng)用津畸,SERVLET應(yīng)用或REACTIVE應(yīng)用。
#3 加載spring.factories中配置的ApplicationContextInitializer實現(xiàn)類必怜,將結(jié)果存放到SpringApplication#initializers
#4 加載spring.factories中配置的ApplicationListener實現(xiàn)類洼畅,將結(jié)果存放到SpringApplication#listeners
#5 獲取main方法所在Class

SpringApplication#getSpringFactoriesInstances -> SpringFactoriesLoader#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));   //#1
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource); //#2
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());   //#3
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

#1 FACTORIES_RESOURCE_LOCATION就是字符串"META-INF/spring.factories",這里讀取jar中META-INF/spring.factories文件內(nèi)容
#2 加載spring.factories文件(格式為Properties)
#3 讀取Properties內(nèi)容棚赔,緩存結(jié)果

spring.factories格式為

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

key為spring擴(kuò)展接口(或聲明功能的注解),value為對應(yīng)的功能實現(xiàn)類的列表

SpringApplication#run

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();  // #1
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();    // #2
    SpringApplicationRunListeners listeners = getRunListeners(args);    // #3
    listeners.starting();   // #4
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  // #5
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);  // #6
        configureIgnoreBeanInfo(environment);   // #7
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();   // #8
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);   // #9
        refreshContext(context);    // #10
        afterRefresh(context, applicationArguments);    // #11
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context); // #12
        callRunners(context, applicationArguments); // #13
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context); // #14
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

#1 開始計時(計算初始化需要花費多少時間)
#2 配置java.awt.headless
#3 獲取spring.factories中配置的SpringApplicationRunListener實現(xiàn)類
SpringApplicationRunListeners是SpringApplicationRunListener的組合徘郭,SpringApplicationRunListener是SpringBoot中新增接口靠益,通過它來間接調(diào)用 ApplicationListener。
該接口實現(xiàn)類為EventPublishingRunListener残揉,他的構(gòu)造方法中需傳入SpringApplication胧后,并獲取SpringApplication#listeners屬性。
SpringApplicationRunListener組件很重要抱环,SpringBoot中很多擴(kuò)展也是通過listerner實現(xiàn)的壳快,如日志系統(tǒng)的啟動
#4 發(fā)送ApplicationStartedEvent事件
#5 命令行參數(shù)處理
#6 構(gòu)建Environment
#7 處理spring.beaninfo.ignore配置
#8 創(chuàng)建ApplicationContext
#9 prepareContext
#10 refreshContext
#11 afterRefresh,預(yù)留擴(kuò)展方法
#12 發(fā)送ApplicationStartedEvent事件
#13 運行ApplicationRunner镇草,CommandLineRunner
#14 發(fā)送ApplicationReadyEvent事件

SpringApplication#prepareEnvironment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableEnvironment environment = getOrCreateEnvironment(); // #1
    configureEnvironment(environment, applicationArguments.getSourceArgs());    // #2
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(environment); // #3
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

#1 創(chuàng)建一個Environment
#2 將SpringApplication#run中的可變參數(shù)列表傳遞給Environment
回到文章開頭說的第3個問題眶痰,關(guān)于MyApplication#run中的args參數(shù)
我們在啟動SpringBoot時,可以添加命令行參數(shù)梯啤,如java -jar MyApplication.jar --spring.profiles.active=dev
命令行參數(shù)--spring.profiles.active=dev會傳遞給main方法竖伯,main方法中需要將其傳遞給SpringApplication#run方法,
這里將命令行參數(shù)添加Environment中因宇,作為一個PropertySource七婴。
必須在main方法中將args參數(shù)傳給SpringApplication#run方法,否則會造成命令行的參數(shù)失效察滑。
#3 發(fā)送ApplicationEnvironmentPreparedEvent事件

SpringApplication#createApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);    // #1
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);   // #2
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);    // #3
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);   // #4
}

#1 SERVLET應(yīng)用打厘,選擇AnnotationConfigServletWebServerApplicationContext
#2 REACTIVE應(yīng)用,選擇AnnotationConfigReactiveWebServerApplicationContext
#3 JAVA應(yīng)用贺辰,選擇AnnotationConfigApplicationContext
#4 構(gòu)造對應(yīng)的Spring Context
AnnotationConfigServletWebServerApplicationContext#構(gòu)造方法 -> AnnotatedBeanDefinitionReader#構(gòu)造方法 ->
AnnotationConfigUtils#registerAnnotationConfigProcessors

這里注冊一些實現(xiàn)SpringBoot功能必須的PostProcessor

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {

    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    if (beanFactory != null) {
        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
            beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
        }
        if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        }
    }

    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); //#1
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);    //#2
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    ...

    return beanDefs;
}

#1 注冊ConfigurationClassPostProcessor户盯,該P(yáng)ostProcessor處理@Configuration等注解
#2 注冊AutowiredAnnotationBeanPostProcessor,該P(yáng)ostProcessor處理@Value魂爪,@Autowired等注解

SpringApplication#prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);    
    postProcessApplicationContext(context);
    applyInitializers(context); //#1
    listeners.contextPrepared(context); //#2
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));  //#3
    listeners.contextLoaded(context);   //#4
}

#1 調(diào)用ApplicationContextInitializer#initialize
#2 調(diào)用SpringApplicationRunListener#contextPrepared方法
#3 getAllSources獲取primarySources以及所有的PropertySource先舷,并解析為BeanDefinition注冊到Spring上下文中(后面要使用)
PropertySource(屬性源)用于Environment重新獲取配置屬性。
primarySources就是SpringApplication#run方法的第一個參數(shù)
回到文章開頭第2個問題滓侍,
通過SpringApplication#run方法的MyApplication.class參數(shù)蒋川,這里將MyApplication的BeanDefinition注冊到Spring上下文中,后面Spring就是可以獲取MyApplication上的@SpringBootApplication等注解了撩笆。
#4 發(fā)送ApplicationContextInitializedEvent事件

SpringApplication#refreshContext
之前spring源碼解析的文章已經(jīng)說過refreshContext操作了捺球。

SpringApplication#callRunners

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());   // #1
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {    // #2
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

#1 獲取ApplicationRunner和CommandLineRunner
#2 調(diào)用對應(yīng)的run方法

如果您覺得本文不錯缸浦,歡迎關(guān)注我的微信公眾號,您的關(guān)注是我堅持的動力氮兵!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裂逐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泣栈,更是在濱河造成了極大的恐慌卜高,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件南片,死亡現(xiàn)場離奇詭異掺涛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)疼进,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門薪缆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伞广,你說我怎么就攤上這事拣帽。” “怎么了嚼锄?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵减拭,是天一觀的道長。 經(jīng)常有香客問我灾票,道長峡谊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任刊苍,我火速辦了婚禮既们,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘正什。我一直安慰自己啥纸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布婴氮。 她就那樣靜靜地躺著斯棒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪主经。 梳的紋絲不亂的頭發(fā)上荣暮,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機(jī)與錄音罩驻,去河邊找鬼穗酥。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砾跃。 我是一名探鬼主播骏啰,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼抽高!你這毒婦竟也來了判耕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤翘骂,失蹤者是張志新(化名)和其女友劉穎壁熄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碳竟,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡请毛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞭亮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡固棚,死狀恐怖统翩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情此洲,我是刑警寧澤厂汗,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站呜师,受9級特大地震影響娶桦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汁汗,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一衷畦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧知牌,春花似錦祈争、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扁藕,卻和暖如春沮峡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亿柑。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工邢疙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓秘症,卻偏偏與公主長得像照卦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乡摹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354