SpringBoot(四)SpringApplication啟動類運(yùn)行階段- SpringApplicationRunListener

前言

????????最近在學(xué)習(xí)Spring Boot相關(guān)的課程,過程中以筆記的形式記錄下來咸灿,方便以后回憶摸吠,同時也在這里和大家探討探討,文章中有漏的或者有補(bǔ)充的较解、錯誤的都希望大家能夠及時提出來畜疾,本人在此先謝謝了!

開始之前呢印衔,希望大家?guī)е鴰讉€問題去學(xué)習(xí):
1啡捶、Spring Boot SpringApplication 是什么?
2奸焙、整體流程或結(jié)構(gòu)是怎樣的瞎暑?
3、重點(diǎn)內(nèi)容或者核心部分是什么与帆?
4了赌、怎么實(shí)現(xiàn)的?
5玄糟、是怎么和 Spring 關(guān)聯(lián)起來的勿她?
這是對自我的提問,我認(rèn)為帶著問題去學(xué)習(xí)茶凳,是一種更好的學(xué)習(xí)方式嫂拴,有利于加深理解。好了贮喧,接下來進(jìn)入主題筒狠。

1、起源

????????上篇文章我們講了 SpringApplication 的準(zhǔn)備階段箱沦,在這個階段辩恼,完成了運(yùn)行時所需要準(zhǔn)備的資源,如:initializers谓形、listeners等灶伊。而這篇文章我們就來講講 SpringApplication 的運(yùn)行階段,在這個階段寒跳,它是如何啟動 Spring 應(yīng)用上下文的聘萨,且如何與 Spring 事件結(jié)合起來,形成完整的 SpringApplication生命周期的童太。

注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOT

2米辐、SpringApplication 運(yùn)行階段

????????上篇文章我們講了 SpringApplication 的構(gòu)造方法胸完,這里我們就來講講 SpringApplication 的核心,也就是run方法翘贮,代碼如下:

public class SpringApplication {

    ...
        
    public ConfigurableApplicationContext run(String... args) {
        // 這是 Spring 的一個計(jì)時器赊窥,計(jì)算代碼的執(zhí)行時間(ms級別)
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        // 這倆變量在后面賦值處進(jìn)行說明
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        
        // 用來設(shè)置java.awt.headless屬性值
        configureHeadlessProperty();
        
        // 該對象屬于組合模式的實(shí)現(xiàn),核心是內(nèi)部關(guān)聯(lián)的 SpringApplicationRunListener 集合狸页,SpringApplicationRunListener 是 Spring Boot 的運(yùn)行時監(jiān)聽器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 會在不同的階段調(diào)用對應(yīng)的方法锨能,這里表示啟動run方法被調(diào)用
        listeners.starting();
        
        try {
        
            // 用來獲取 SpringApplication.run(args)傳入的參數(shù)
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            
            // 獲取 properties 配置文件
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            
            // 設(shè)置 spring.beaninfo.ignore 的屬性值,判斷是否跳過搜索BeanInfo類
            configureIgnoreBeanInfo(environment);
            
            // 這里是項(xiàng)目啟動時芍耘,控制臺打印的 Banner
            Banner printedBanner = printBanner(environment);
            
            // 這里就是創(chuàng)建 Spring 應(yīng)用上下文
            context = createApplicationContext();
            
            // 獲取 spring.factories 中key為 SpringBootExceptionReporter 的類名集合
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
                    
            // 這里是準(zhǔn)備 Spring 應(yīng)用上下文
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            
            // 這里是啟動 Spring 應(yīng)用上下文址遇,底層調(diào)用的是 ApplicationContext 的 refresh() 方法,到這里就正式進(jìn)入了 Spring 的生命周期齿穗,同時傲隶,SpringBoot的自動裝配特性也隨之啟動
            refreshContext(context);
            
            // 里面是空的,猜測應(yīng)該是交由開發(fā)人員自行擴(kuò)展
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            
            // 這里打印啟動信息
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            
            // ApplicationContext 啟動時窃页,調(diào)用該方法
            listeners.started(context);
            
            // 項(xiàng)目啟動后跺株,做的一些操作,開發(fā)人員可自行擴(kuò)展
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
    
        try {
        
            // ApplicationContext 啟動完成時脖卖,調(diào)用該方法
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
    
    ...
}

上面就是整個過程的概覽乒省,可以看到,在運(yùn)行階段執(zhí)行的操作比較多畦木,雖然看起來雜亂無章袖扛,但其實(shí)還是有規(guī)律可循的。比如十籍,執(zhí)行的 SpringApplicationRunListeners 中的階段方法蛆封,剛啟動階段的 starting 、已啟動階段的 started 勾栗、啟動完成階段的 running 等惨篱。還有對應(yīng)的 Spring 應(yīng)用上下文的創(chuàng)建、準(zhǔn)備围俘、啟動操作等砸讳。接下來,就對里面的幾個核心對象進(jìn)行討論界牡。

2.1 SpringApplicationRunListeners 結(jié)構(gòu)

我們先來看看 SpringApplicationRunListeners 對象簿寂,從代碼可以看出該對象是由 getRunListeners 方法創(chuàng)建的:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

可以看到,通過傳入的 getSpringFactoriesInstances 方法的返回值宿亡,執(zhí)行 SpringApplicationRunListeners 的構(gòu)造方法常遂,進(jìn)行對象的創(chuàng)建。接著看 getSpringFactoriesInstances 方法:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

看到這大家應(yīng)該比較熟悉了挽荠,通過前面幾篇文章的討論我們知道烈钞,該方法通過 SpringFactoriesLoader.loadFactoryNames 返回所有 classpass 下的 spring.factories 文件中 key 為 SpringApplicationRunListener 的實(shí)現(xiàn)類集合泊碑。如 Spring Boot 的內(nèi)建實(shí)現(xiàn):

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

最后,就是將該集合傳入 SpringApplicationRunListeners 的構(gòu)造方法:

class SpringApplicationRunListeners {

    ...

    private final List<SpringApplicationRunListener> listeners;

    SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
        this.log = log;
        this.listeners = new ArrayList<>(listeners);
    }

    public void starting() {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.starting();
        }
    }

    ...

}

里面是將集合賦值到 listeners 屬性毯欣,可以看到 SpringApplicationRunListeners 屬于組合模式的實(shí)現(xiàn),核心其實(shí)是內(nèi)部關(guān)聯(lián)的 SpringApplicationRunListener 對象集合臭脓,當(dāng)外部調(diào)用該階段方法時酗钞,就會迭代執(zhí)行集合中 SpringApplicationRunListener 對應(yīng)的方法。所以接下來我們就來討論 SpringApplicationRunListener来累。

2.1.1 SpringApplicationRunListener 事件和監(jiān)聽機(jī)制

SpringApplicationRunListener 負(fù)責(zé)在 SpringBoot 的不同階段廣播相應(yīng)的事件砚作,然后調(diào)用實(shí)際的 ApplicationListener 類,在該類的 onApplicationEvent 方法中嘹锁,根據(jù)不同的 Spring Boot 事件執(zhí)行相應(yīng)操作葫录。整個過程大概如此,接下來進(jìn)行詳細(xì)討論领猾,先來看看 SpringApplicationRunListener 定義:

public interface SpringApplicationRunListener {

    // 在run()方法開始執(zhí)行時被調(diào)用米同,表示應(yīng)用剛剛啟動,對應(yīng)的 Spring Boot 事件為 ApplicationStartingEvent
    void starting();

    // ConfigurableEnvironment 構(gòu)建完成時調(diào)用摔竿,對應(yīng)的 Spring Boot 事件為 ApplicationEnvironmentPreparedEvent
    void environmentPrepared(ConfigurableEnvironment environment);

    // ApplicationContext 構(gòu)建完成時調(diào)用面粮,對應(yīng)的 Spring Boot 事件為 ApplicationContextInitializedEvent
    void contextPrepared(ConfigurableApplicationContext context);

    // ApplicationContext 完成加載但還未啟動時調(diào)用,對應(yīng)的 Spring Boot 事件為 ApplicationPreparedEvent
    void contextLoaded(ConfigurableApplicationContext context);

    // ApplicationContext 已啟動继低,但 callRunners 還未執(zhí)行時調(diào)用熬苍,對應(yīng)的 Spring Boot 事件為 ApplicationStartedEvent
    void started(ConfigurableApplicationContext context);

    // ApplicationContext 啟動完畢被調(diào)用,對應(yīng)的 Spring Boot 事件為 ApplicationReadyEvent
    void running(ConfigurableApplicationContext context);

    // 應(yīng)用出錯時被調(diào)用袁翁,對應(yīng)的 Spring Boot 事件為 ApplicationFailedEvent
    void failed(ConfigurableApplicationContext context, Throwable exception);

}

我們來看看它的實(shí)現(xiàn)類柴底,也就是上面加載的 spring.factories 文件中的 EventPublishingRunListener 類,該類也是 Spring Boot 內(nèi)建的唯一實(shí)現(xiàn)類粱胜,具體廣播事件的操作在該類中進(jìn)行柄驻,代碼如下:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

    private final SpringApplication application;

    private final String[] args;

    private final SimpleApplicationEventMulticaster initialMulticaster;

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

    @Override
    public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

    ...

}

可以看到,通過構(gòu)造方法創(chuàng)建 EventPublishingRunListener 實(shí)例的過程中年柠,調(diào)用了 getListeners 方法凿歼,將 SpringApplication 中所有 ApplicationListener 監(jiān)聽器關(guān)聯(lián)到了 initialMulticaster 屬性中。沒錯冗恨,這里的 ApplicationListener 監(jiān)聽器就是上篇文章中在 SpringApplication 準(zhǔn)備階段從 spring.factories 文件加載的 key 為 ApplicationListener 的實(shí)現(xiàn)類集合答憔,該實(shí)現(xiàn)類集合全部重寫了 onApplicationEvent 方法。

2.1.2 SimpleApplicationEventMulticaster 廣播器

這里又引出了另一個類掀抹, 也就是 SimpleApplicationEventMulticaster 虐拓,該類是 Spring 的事件廣播器,也就是通過它來廣播各種事件傲武。接著蓉驹,當(dāng)外部迭代的執(zhí)行到 EventPublishingRunListenerstarting 方法時城榛,會通過 SimpleApplicationEventMulticastermulticastEvent 方法進(jìn)行事件的廣播,這里廣播的是 ApplicationStartingEvent 事件态兴,我們進(jìn)入 multicastEvent 方法:

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    ...
    
    @Override
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event));
    }

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }
}

通過 getApplicationListeners 方法狠持,根據(jù)事件類型返回從上面關(guān)聯(lián)的 ApplicationListener 集合中篩選出匹配的 ApplicationListener 集合,根據(jù) Spring Boot 版本的不同瞻润,在這個階段獲取到的監(jiān)聽器也有可能不同喘垂,如 2.1.6.BUILD-SNAPSHOT 版本返回的是:

image

然后依次遍歷這些監(jiān)聽器,同步或異步的調(diào)用 invokeListener 方法:

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

...

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                // -> let's suppress the exception and just log a debug message.
                Log logger = LogFactory.getLog(getClass());
                if (logger.isTraceEnabled()) {
                    logger.trace("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

可以看到绍撞,最終調(diào)用的是 doInvokeListener 方法正勒,在該方法中執(zhí)行了 ApplicationListeneronApplicationEvent 方法,入?yún)閺V播的事件對象傻铣。我們就拿其中一個的監(jiān)聽器來看看 onApplicationEvent 中的實(shí)現(xiàn)章贞,如 BackgroundPreinitializer 類:

public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

    ...
    
    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
        if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
                && event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) {
            performPreinitialization();
        }
        if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
                && preinitializationStarted.get()) {
            try {
                preinitializationComplete.await();
            }
            catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    ...
}

在該方法中,通過 instanceof 判斷事件的類型非洲,從而進(jìn)行相應(yīng)的操作鸭限。該監(jiān)聽器主要的操作是新建一個后臺線程去執(zhí)行那些耗時的初始化工作,包括驗(yàn)證器怪蔑、消息轉(zhuǎn)換器等里覆。LoggingApplicationListener 監(jiān)聽器則是對 Spring Boot 的日志系統(tǒng)做一些初始化的前置操作。另外兩個監(jiān)聽器在該階段無任何操作缆瓣。

至此喧枷,SpringBoot 事件機(jī)制的整體流程大概如此,我們簡要回顧一下幾個核心組件:

  • SpringApplicationRunListeners:首先弓坞,在 run 方法的執(zhí)行過程中隧甚,通過該類在 SpringBoot 不同的階段調(diào)用不同的階段方法,如在剛啟動階段調(diào)用的 starting 方法渡冻。
  • SpringApplicationRunListener:而 SpringApplicationRunListeners 屬于組合模式的實(shí)現(xiàn)戚扳,它里面關(guān)聯(lián)了 SpringApplicationRunListener 實(shí)現(xiàn)類集合,當(dāng)外部調(diào)用階段方法時族吻,會迭代執(zhí)行該集合中的階段方法帽借。實(shí)現(xiàn)類集合是 spring.factories 文件中定義好的類。這里是一個擴(kuò)展點(diǎn)超歌,詳細(xì)的后面述說砍艾。
  • EventPublishingRunListener:該類是 Spring Boot 內(nèi)置的 SpringApplicationRunListener 唯一實(shí)現(xiàn)類,所以巍举,當(dāng)外部調(diào)用各階段的方法時脆荷,真正執(zhí)行的是該類中的方法。
  • SimpleApplicationEventMulticaster:在階段方法中,會通過 SpringSimpleApplicationEventMulticaster 事件廣播器蜓谋,廣播各個階段對應(yīng)的事件梦皮,如這里的 starting 方法廣播的事件是 ApplicationStartingEvent
  • ApplicationListener:最后 ApplicationListener 的實(shí)現(xiàn)類也就是 Spring Boot 監(jiān)聽器會監(jiān)聽到廣播的事件桃焕,根據(jù)不同的事件剑肯,進(jìn)行相應(yīng)的操作。這里的 Spring Boot 監(jiān)聽器是也是在 spring.factories 中定義好的覆旭,這里我們也可自行擴(kuò)展退子。

到這里 Spring Boot 事件監(jiān)聽機(jī)制差不多就結(jié)束了,值得注意的是 Spring Boot 監(jiān)聽器實(shí)現(xiàn)的是 SpringApplicationListener 類型将,事件類最終繼承的也是 SpringApplicationEvent 類,所以荐虐,Spring Boot 的事件和監(jiān)聽機(jī)制都基于 Spring 而實(shí)現(xiàn)的七兜。

2.2 ApplicationArguments 加載啟動參數(shù)

????????當(dāng)執(zhí)行完 listeners.starting 方法后,接著進(jìn)入構(gòu)造 ApplicationArguments 階段:

public class SpringApplication {

    ...
    
    public ConfigurableApplicationContext run(String... args) {
        
        ...
        
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            
            ...
        }
    }
    
    ...
}

該類是用于簡化 Spring Boot 應(yīng)用啟動參數(shù)的封裝接口福扬,我們啟動項(xiàng)目時輸入的命令參數(shù)會封裝在該類中腕铸。一種是通過 IDEA 輸入的參數(shù),如下:

image

另一種是 springboot jar包運(yùn)行時傳遞的參數(shù):
cmd中運(yùn)行java -jar xxx.jar name=張三 pwa=123 铛碑。

然后狠裹,可以通過 @Autowired 注入 ApplicationArguments 的方式進(jìn)行使用:

public class Test {

    @Autowired
    private ApplicationArguments applicationArguments;

    public void getArgs() {
        // 獲取 args 中的所有 non option 參數(shù)
        applicationArguments.getNonOptionArgs();

        // 獲取 args 中所有的 option 參數(shù)的 name
        applicationArguments.getOptionNames();

        // 獲取傳遞給應(yīng)用程序的原始未處理參數(shù)
        applicationArguments.getSourceArgs();

        // 獲取 args 中指定 name 的 option 參數(shù)的值
        applicationArguments.getOptionValues("nmae");

        // 判斷從參數(shù)中解析的 option 參數(shù)是否包含指定名稱的選項(xiàng)
        applicationArguments.containsOption("name");
    }
}

2.3 ConfigurableEnvironment 加載外部化配置

????????接著進(jìn)入構(gòu)造 ConfigurableEnvironment 的階段,該類是用來處理我們外部化配置的汽烦,如 properties涛菠、YAML 等,提供對配置文件的基礎(chǔ)操作撇吞。當(dāng)然俗冻,它能處理的外部配置可不僅僅如此,詳細(xì)的在下篇文章討論牍颈,這里我們進(jìn)行簡要了解即可迄薄,進(jìn)入創(chuàng)建該類的 prepareEnvironment 方法:

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

這里通過 getOrCreateEnvironment 方法返回具體的 Environment

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

可以看到,這里通過 webApplicationType 屬性來判斷當(dāng)前應(yīng)用的類型煮岁,有 Servlet 讥蔽、 Reactive 、 非Web 3種類型画机,該屬性也是在上篇文章中 SpringApplication 準(zhǔn)備階段確定的冶伞,這里我們通常都是 Servlet 類型,返回的是 StandardServletEnvironment 實(shí)例色罚。

之后碰缔,還調(diào)用了 SpringApplicationRunListenersenvironmentPrepared 階段方法,表示 ConfigurableEnvironment 構(gòu)建完成戳护,同時向 Spring Boot 監(jiān)聽器發(fā)布 ApplicationEnvironmentPreparedEvent 事件金抡。監(jiān)聽該事件的監(jiān)聽器有:

image

2.4 ConfigurableApplicationContext 創(chuàng)建 Spring 應(yīng)用上下文

????????這里通過 createApplicationContext 方法創(chuàng)建 Spring 應(yīng)用上下文瀑焦,實(shí)際上 Spring 的應(yīng)用上下文才是驅(qū)動 Spring Boot 的核心引擎:

public class SpringApplication {

    ...

    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

    public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

    public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

    ...

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

這里也是通過 webApplicationType 屬性來確定應(yīng)用類型從而創(chuàng)建 String 上下文,上篇文章說到該屬性值是在 Spring Boot 準(zhǔn)備階段推導(dǎo)出來的梗肝。這里我們的應(yīng)用類型是 Servlet 榛瓮,所以創(chuàng)建的是 AnnotationConfigServletWebServerApplicationContext 對象。創(chuàng)建完 Spring 應(yīng)用上下文之后巫击,執(zhí)行 prepareContext 方法進(jìn)入準(zhǔn)備上下文階段:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    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]));
    listeners.contextLoaded(context);
}

我們來看看主要做了哪些操作:

  1. 設(shè)置了 Spring 應(yīng)用上下文的 ApplicationArguments禀晓,上面說過是處理外部化配置的,具體類型為 StandardServletEnvironment 坝锰。
  2. Spring 應(yīng)用上下文后置處理粹懒,主要是覆蓋當(dāng)前 Spring 應(yīng)用上下文默認(rèn)所關(guān)聯(lián)的 ResourceLoaderClassLoader
  3. 執(zhí)行 Spring 的初始化器顷级,上篇文章說過在 Spring Boot 準(zhǔn)備階段初始化了一批在 spring.factories 文件中定義好的 ApplicationContextInitializer 凫乖,這里就是執(zhí)行它們的 initialize 方法,同時這里也是一個擴(kuò)展點(diǎn)弓颈,后面詳細(xì)討論帽芽。
  4. 執(zhí)行 SpringApplicationRunListenerscontextPrepared 階段方法,表示 ApplicationContext 準(zhǔn)備完成翔冀,同時向 Spring Boot 監(jiān)聽器發(fā)布 ApplicationContextInitializedEvent 事件 导街。
  5. springApplicationArgumentsspringBootBanner 注冊為 Bean
  6. 加載 Spring 應(yīng)用上下文的配置源纤子,也是在上篇文章 Spring Boot 準(zhǔn)備階段獲取的 primarySourcessources 搬瑰,primarySources 來源于 SpringApplication 構(gòu)造器參數(shù),sources 則來源于自定義配置的 setSources 方法计福。
  7. 最后執(zhí)行 SpringApplicationRunListenerscontextLoaded 階段方法跌捆,表示 ApplicationContext 完成加載但還未啟動,同時向 Spring Boot 監(jiān)聽器發(fā)布 ApplicationPreparedEvent 事件 象颖。

接下來就是真正啟動階段佩厚,執(zhí)行的是 refreshContext 方法:

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}
protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

可以看到,底層調(diào)用的是 AbstractApplicationContextrefresh 方法说订,到這里 Spring 應(yīng)用正式啟動抄瓦,Spring Boot 核心特性也隨之啟動,如自動裝配陶冷。隨后執(zhí)行 SpringApplicationRunListenersstarted 階段方法钙姊,表示 ApplicationContext 已啟動,同時向 Spring Boot 監(jiān)聽器發(fā)布 ApplicationStartedEvent 事件 埂伦。但還未啟動完成煞额,后面還有一個 callRunners 方法,一般來講,里面執(zhí)行一些我們自定義的操作膊毁。之后 Spring 應(yīng)用才算啟動完成胀莹,隨后調(diào)用 running 方法,發(fā)布 ApplicationReadyEvent 事件婚温。至此描焰,SpringApplication 運(yùn)行階段結(jié)束。

3栅螟、總結(jié)

????????最后來對 SpringApplication 運(yùn)行階段做一個總結(jié)荆秦。這個階段核心還是以啟動 Spring 應(yīng)用上下文為主,同時根據(jù)應(yīng)用類型來初始化不同的上下文對象力图,但這些對象的基類都是 SpringConfigurableApplicationContext 類步绸。且在啟動的各個階段中,使用 SpringApplicationRunListeners 進(jìn)行事件廣播吃媒,回調(diào) Spring Boot 的監(jiān)聽器靡努。同時還初始化了 ApplicationArgumentsConfigurableEnvironment 等幾個組件晓折。下篇文章我們就來討論 Spring Boot 的外部化配置部分,來看看為什么外部的各個組件兽泄,如 Redis漓概、Dubbo 等在 properties 文件中進(jìn)行相應(yīng)配置后,就可以正常使用病梢。

以上就是本章的內(nèi)容胃珍,如過文章中有錯誤或者需要補(bǔ)充的請及時提出,本人感激不盡蜓陌。



參考:

《Spring Boot 編程思想》
https://www.cnblogs.com/youzhibing/p/9603119.html
http://www.reibang.com/p/b86a7c8b3442
https://www.cnblogs.com/duanxz/p/11243271.html
http://www.reibang.com/p/7a674c59d76e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末觅彰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钮热,更是在濱河造成了極大的恐慌填抬,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隧期,死亡現(xiàn)場離奇詭異飒责,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)仆潮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門已卸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來批销,“玉大人,你說我怎么就攤上這事∠己眨” “怎么了钠惩?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我屏歹,道長,這世上最難降的妖魔是什么芥喇? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任西采,我火速辦了婚禮,結(jié)果婚禮上继控,老公的妹妹穿的比我還像新娘械馆。我一直安慰自己,他們只是感情好武通,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布霹崎。 她就那樣靜靜地躺著,像睡著了一般冶忱。 火紅的嫁衣襯著肌膚如雪尾菇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天囚枪,我揣著相機(jī)與錄音派诬,去河邊找鬼。 笑死链沼,一個胖子當(dāng)著我的面吹牛默赂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播括勺,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼缆八,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了疾捍?” 一聲冷哼從身側(cè)響起奈辰,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乱豆,沒想到半個月后奖恰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咙鞍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年房官,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片续滋。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡翰守,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疲酌,到底是詐尸還是另有隱情蜡峰,我是刑警寧澤了袁,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站湿颅,受9級特大地震影響载绿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜油航,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一崭庸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谊囚,春花似錦怕享、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奠伪,卻和暖如春跌帐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绊率。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工谨敛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滤否。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓佣盒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親顽聂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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