微服務 SpringBoot 2.0(四):啓動剖析之SpringApplication.run

我覺得只是運行了個main方法,可卻啓動了服務 —— Java面試必修php

引言

前一章咱們講解了啓動原理的@SpringBootApplication部分,仔細跟着看代碼仍是很好理解,若需觀看@SpringBootApplication註解部分請點擊,接下來這章主要講解run方法運行部分,run方法運行這裏很枯燥乏味,因此請帶好你的精氣神,準備好水,由於實在是太乾了。css

運行啓動

工具

  • SpringBoot版本:2.0.4
  • 開發工具:IDEA 2018
  • Maven:3.3 9
  • JDK:1.8

咱們繼續看這段啓動代碼java

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

 

SpringApplication.run分析

分析該方法主要分兩部分,一是SpringApplication實例化,二是run方法的執行。react

#該方法返回一個ConfigurableApplicationContext對象 SpringApplication.run(MySpringConfigurationApp.class, args);//參數1-應用入口的類,參數2-命令行參數 
分析點1、SpringApplication實例化

在SpringApplication實例初始化的時候,它會作 4 件有意義的事情:web

  1. 推斷應用類型是Standard仍是Web
    可能會出現三種結果REACTIVE 、NONESERVLET :面試

    this.webApplicationType = this.deduceWebApplicationType();//該行代碼位於構造器中 
  2. 查找並加載全部可用初始化器,設置到initializers屬性中spring

    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));//spring.factories處讀取 
  3. 找出全部的應用程序監聽器,設置到listeners屬性中。緩存

    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));//該行代碼位於構造器中 
  4. 推斷並設置main方法的定義類,意思就是找出運行的主類。ruby

    this.mainApplicationClass = this.deduceMainApplicationClass();//該行代碼位於構造器中 

至此,對於SpringApplication實例的初始化過程就結束了,接下來進入方法調用環節。bash


分析點2、SpringApplication.run方法調用

接下來的過程比較枯燥無味,若是您跟着仔細看完,你必定有所收穫。
執行流程圖源文件:https://www.processon.com/view/link/5b825917e4b0d4d65be7066aPS 畫圖好累

 
執行流程圖

流程圖對應的代碼以下

public ConfigurableApplicationContext run(String... args) {
        //計時工具
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();

        // step1  設置java.awt.headless系統屬性爲true - 沒有圖形化界面
        this.configureHeadlessProperty();

        //step2  初始化監聽器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);

        //step3  啓動已準備好的監聽器,發佈應用啓動事件
        listeners.starting();

        Collection exceptionReporters;
        try {

            //step4  根據SpringApplicationRunListeners以及參數來裝配環境,如java -jar xx.jar --server.port=8000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);

            //step5  打印Banner 就是啓動Spring Boot的時候打印在console上的ASCII藝術字體
            Banner printedBanner = this.printBanner(environment);

            //step6  建立Spring ApplicationContext()上下文
            context = this.createApplicationContext();

            //step7  準備異常報告器
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, 
             new Class[]{ConfigurableApplicationContext.class}, context);

            //step8  裝配Context,上下文前置處理
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            //step9  Spring上下文刷新處理
            this.refreshContext(context);

            //step10  Spring上下文後置結束處理 
            this.afterRefresh(context, applicationArguments);

            // 中止計時器監控
            stopWatch.stop();

            //輸出日誌記錄執行主類名、時間信息
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            //step11  發佈應用上下文啓動完成事件
            listeners.started(context);

            //step12  執行全部 Runner 運行器
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            //step13  發佈應用上下文就緒事件
            listeners.running(context);

            //step14  返回應用上下文
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

 

代碼解析
step1 java.awt.headless系統屬性設置
this.configureHeadlessProperty(); 

Headless模式是系統的一種配置模式。設置該默認值爲:true,Java.awt.headless = true 有什麼做用?

對於一個 Java 服務器來講常常要處理一些圖形元素,例如地圖的建立或者圖形和圖表等。這些API基本上老是須要運行一個X-server以便能使用AWT(Abstract Window Toolkit,抽象窗口工具集)。然而運行一個沒必要要的 X-server 並非一種好的管理方式。有時你甚至不能運行 X-server,所以最好的方案是運行 headless 服務器,來進行簡單的圖像處理。

方法聲明以下:
private void configureHeadlessProperty() {
        System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));
}

 

step2 初始化監聽器
SpringApplicationRunListeners listeners = this.getRunListeners(args); 

該方法最終落到了loadSpringFactories方法上,加載了META-INF/spring.factories這個配置文件下的全部資源,並放入緩存,而後再獲取了org.springframework.context.ApplicationListener定義的資源列表。這些也就是SpringBoot的自動裝配資源,而後獲取到後存放到list中,並調用createSpringFactoriesInstances函數建立了SpringFactories的實例。

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

 

META-INF/spring.factories文件部分:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

 

step3 啓動已準備好的監聽器
listeners.starting(); 

經過step2操做拿到監聽器集合了以後,再統一遍歷出來後,爲每一個Listener都分配了一個任務線程去啓動它們

方法聲明以下:
public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

}

 

step4 裝配環境參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 

咱們來看一下new DefaultApplicationArguments(args)這個構造函數,跟蹤進去發現調用的SimpleCommandLineArgsParser.parse函數,從下面代碼中,能夠看到該方法讀取了命令行參數:

方法聲明以下:
public CommandLineArgs parse(String... args) {
        CommandLineArgs commandLineArgs = new CommandLineArgs();
        String[] var3 = args;
        int var4 = args.length;

        //省略讀取命令行代碼...
        return commandLineArgs;
    }

 

tip:那到這裏你是否會聯想到:java -jar xx.jar --server.port=8000後面的參數呢
接着咱們看下一行:

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); 

經過這行代碼咱們能夠看到spring boot把前面建立出來的listeners和命令行參數,傳遞到prepareEnvironment函數中來準備運行環境。來看一下prepareEnvironment函數的真面目:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, 
ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary(
(ConfigurableEnvironment)environment);
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

 

在這裏咱們看到了環境是經過getOrCreateEnvironment建立出來的,再深挖一下getOrCreateEnvironment的源碼:

private ConfigurableEnvironment getOrCreateEnvironment() {
        //若是environment 已經存在,則直接返回當前的環境。思考:究竟什麼狀況下回存在呢?
        if (this.environment != null) {
            return this.environment;
        } else {
            //判斷webApplicationType是否是SERVLET,若是是,則建立Servlet的環境,不然建立基本環境
            return (ConfigurableEnvironment)(this.webApplicationType == WebApplicationType.SERVLET
             ? new StandardServletEnvironment() : new StandardEnvironment());
        }
    }

 

接着咱們看下一行:

this.configureIgnoreBeanInfo(environment);
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        //若是System中的spring.beaninfo.ignore屬性爲空,就把當前環境中的屬性覆蓋上去
        //spring.beaninfo.ignore= true 跳過搜索BeanInfo類
        if (System.getProperty("spring.beaninfo.ignore") == null) {
            Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
            System.setProperty("spring.beaninfo.ignore", ignore.toString());
        }

    }

 

step5 打印Banner
Banner printedBanner = this.printBanner(environment); 

spring boot啓動的時候,控制檯顯示的那個藝術字體圖案

step6 建立Spring上下文
context = this.createApplicationContext(); 

spring boot是根據不一樣的webApplicationType的類型,來建立不一樣的ApplicationContext

代碼聲明以下:
protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

 

step7 準備異常報告器

繼續來看下一步,準備異常報告,又特麼是這個getSpringFactoriesInstances

exceptionReporters = getSpringFactoriesInstances(
    SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context); 

你們能夠再仔細看看META-INF/spring.factories文件

# Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers 
step8 裝配Context,上下文前置處理
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 

這行代碼是把上面已經建立好的對象,傳遞給prepareContext來準備上下文

prepareContext()聲明以下:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, 
    SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        //這裏傳入的是 StandardServletEnvironment
        context.setEnvironment(environment);
        //設置上下文的beanNameGenerator(bean生成器)和resourceLoader(資源加載器)
        this.postProcessApplicationContext(context);
        //拿到以前實例化SpringApplication對象的時候設置的ApplicationContextInitializer,調用它們的initialize方法,對上下文作初始化
        this.applyInitializers(context);
        //調用listeners#contextPrepared,該方法是一個空實現,之後咱們能夠擴展的地方
        listeners.contextPrepared(context);
        //打印啓動日誌
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
        //往上下文的beanFactory中註冊一個singleton的bean,bean的名字是springApplicationArguments,bean的實例是以前實例化的ApplicationArguments對象
        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);

        //若是以前獲取的printedBanner不爲空
        if (printedBanner != null) {
        //往上下文的beanFactory中註冊一個singleton的bean,bean的名字是springBootBanner,bean的實例就是這個printedBanner.這裏默認是SpringBootBanner.
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        //獲取全部資源
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //調用load方法註冊啓動類的bean定義,也就是調用SpringApplication.run(Application.class, args);的類
        //SpringApplication的load方法內會建立BeanDefinitionLoader的對象,並調用它的load()方法
        this.load(context, sources.toArray(new Object[0]));
        //上下文已經加載,該方法先找到全部的ApplicationListener,遍歷這些listener
        //若是該listener繼承了ApplicationContextAware類,這一步會調用它的setApplicationContext方法,設置context
        listeners.contextLoaded(context);
    }

 

step9 Spring上下文刷新 refreshContext
this.refreshContext(context); 

refreshContext函數中,第一行調用了refresh(context);跳轉了一下,下面的代碼是註冊了一個應用關閉的函數鉤子,以下

private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
                ;
            }
        }

}

 

經過代碼跟蹤分析發現,實際上是調用了AbstractApplicationContext中的refresh方法。
ServletWebServerApplicationContext和ReactiveWebServerApplicationContext的refresh函數中都是調用了super.refresh();

代碼以下:
public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        //同步快執行刷新操做
        synchronized(this.startupShutdownMonitor) {
            //準備刷新上下文
            this.prepareRefresh();
            //獲取了bean工廠之後,設置bean工廠的環境
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                //發送了一個bean工廠的處理信號,緊接着回調處理器,註冊到bean工廠
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                //初始化監聽器
                this.initMessageSource();
                //初始化監聽管理器
                this.initApplicationEventMulticaster();
                this.onRefresh();
                //把spring容器內的listener和beanfactory的listener都添加到廣播器中:
                this.registerListeners();
                //實例化BeanFactory 中已經被註冊可是沒被實例化的全部實例。初始化的過程當中各類BeanPostProcessor已經開始生效
                this.finishBeanFactoryInitialization(beanFactory);
                //收尾,刷新產生的緩存、初始化生命週期處理器LifecycleProcessor,並調用其onRefresh()方法、發佈事件、調用LiveBeansView的registerApplicationContext註冊context。
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }

 

step10 Spring上下文後置結束處理 afterRefresh

在刷新完context後,調用了一個afterRefresh函數,這個函數前面已經說過了,是爲了給ApplicationContext的子類留下的一個擴展點

this.afterRefresh(context, applicationArguments); 

而後調用了listeners.started(context);,把監聽器設置成了已經啓動的狀態。
最後調用了callRunners函數,獲取全部的ApplicationRunnerCommandLineRunner而後調用他們的run方法:

step11 發佈應用上下文啓動完成事件

把監聽器設置成了已經啓動的狀態

listeners.started(context); 
step12 執行全部Runner運行器

調用了callRunners函數,獲取全部的ApplicationRunnerCommandLineRunner而後調用他們的run方法

step13 發佈應用上下文就緒事件
listeners.running(context); 

觸發全部 SpringApplicationRunListener監聽器的 running 事件方法。

step14 返回上下文

總結

本文分析了Spring Boot啓動run方法,看得真的是讓人頭痛,但又不得不看,主要分爲如下2步進行分析

  1. SpringApplication實例的構建過程
    其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都經過META-INF/spring.factories完成定義。

  2. SpringApplication實例run方法的執行過程
    其中主要有一個SpringApplicationRunListeners的概念,它做爲Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication實例的構建過程當中獲得的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴展性。

若是從可擴展性的角度出發,應用開發者能夠在Spring Boot容器的啓動階段,擴展哪些內容呢:

  • 初始化器(Initializer)
  • 監聽器(Listener)
  • 容器刷新後置Runners(ApplicationRunner或者CommandLineRunner接口的實現類)
  • 啓動期間在Console打印Banner的具體實現類

做者有話說:喜歡的話就請移步Java面試必修網,請自備水,更多幹、幹、乾貨等着你

相關文章
相關標籤/搜索