SpirngBoot | 啓動原理 01

SpirngBoot

一個讀者,也是個人好朋友投稿的一篇關於 SpringBoot 啓動原理的文章,才大二就如此優秀,將來可期。前端


我一直想了解一下 SpirngBoot 的是如何啓動的,我想就來寫一篇關於 SpirngBoot 啓動分析吧。第一次寫那麼高深的技術話題理解不到位的話也請多多包涵。java

源碼版本

SpinrgBoot 2.0.2react

衆所周知 SpringBoot 的啓動類是在一個 main 方法中調用 SpringApplication.run() 方法啓動的,如:web

@SpringBootApplication
public class DiveInSpringBootApplication {

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

}

啓動順序分析以下:算法

初始化階段 -> 運行階段spring

初始化階段:

進入run方法中,SpringApplication.run() 會先爲其建立一個 SpringApplication 對象:緩存

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //加載應用資源(URL資源、File資源、ClassPath資源)
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // primarySources 爲 run 方法傳入的引導類
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //推斷Web應用類型
        this.webApplicationType = deduceWebApplicationType();
        //加載應用上下文(初始化Initializers)
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //加載應用事件監聽器(初始化ApplicationListener)
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //推斷引導類
        this.mainApplicationClass = deduceMainApplicationClass();
    }

Step1. 經過 deduceWebApplicationType() 來推斷咱們Web類型應用服務器

private WebApplicationType deduceWebApplicationType() {
    
    //根據當前應用的ClassPath中是否存在相關實現類來推斷Web類型
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
        && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {

        return WebApplicationType.REACTIVE;
    }

    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }

    return WebApplicationType.SERVLET;
}

看看使用到的 3 個常量值:app

常量值 應用類型
REACTIVE_WEB_ENVIRONMENT_CLASS org.springframework.web.reactive.DispatcherHandler
MVC_WEB_ENVIRONMENT_CLASS org.springframework.web.servlet.DispatcherServlet
WEB_ENVIRONMENT_CLASSES {"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }

也就是說,有以下三種狀況:less

  1. 若是應用程序中存在 org.springframework.web.reactive.DispatcherHandler 這個類,則表示是一個響應式 web 應用,項目在啓動時,須要去加載啓動內嵌的響應式 web 服務器。
  2. 若是應用程序中既不存在 javax.servlet.Servlet 類,也不存在org.springframework.web.context.ConfigurableWebApplicationContext 這個類,則

表示當前應用不是一個web應用,啓動時無需加載啓動內嵌的 web 服務器。

  1. 除上述兩種狀況外,則表示當前應用是一個 servlet 的 web 應用,啓動時須要加載啓動內嵌的 servlet 的 web 服務器(好比 Tomcat )。

Step2. 如何加載應用上下文初始器(初始化 Initializers )

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
    Class<?>[] parameterTypes, Object... args) {

    ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 

    // 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;
}

利用Spirng的工廠加載機制,實例化ApplicationContextInitializer實現類,並排序集合。具體實現方法以下:

  1. 經過 SpringFactoriesLoader.loadFactoryNames 來掃描 META-INF/spring.factories 下符合 ApplicationContextInitializer 類型的資源名稱。
  2. 實例化全部在 META-INF/spring.factories 下找到的資源信息
  3. 對實例化的資源信息進行優先級順序排序,或經過@order註解和Ordered接口進行排序
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
#Spring容器的常見的錯誤配置警告
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
#設置Spring應用上下文ID
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

Step3. 加載應用事件監聽器( ApplicationListener )

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,

    Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    
        // 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;
}

利用 Spring 工廠加載機制,實例化 ApplicationListene r實現類,並排序對象集合,具體方法跟上面 初始化Initializers 相似,不贅述。

# Application Listeners
org.springframework.context.ApplicationListener=\
#Spring應用上下文加載完成以後清除緩存
org.springframework.boot.ClearCachesApplicationListener,\
#父容器關閉時通知各個子容器關閉,
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
#文件編碼
org.springframework.boot.context.FileEncodingApplicationListener,\
#控制檯彩色輸出
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
#外部化配置 管理factories或者YMAL文件
org.springframework.boot.context.config.ConfigFileApplicationListener,\
#將指定事件廣播給指定的監聽器
org.springframework.boot.context.config.DelegatingApplicationListener,\
#將須要輸出的日誌打印到指定的級別 DEBUG INFO ERROR
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
#初始化日誌系統
org.springframework.boot.context.logging.LoggingApplicationListener,\
#控制可執行Spirng文件版本
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

Step4. 推斷引導類

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
                //根據 Main 線程執行堆棧來判斷實際的引導類。
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
            // Swallow and continue
    }
    return null;
}

運行階段

整個 SpringApplication 圍繞着 run 這個方法並分爲兩個小階段:

  • 加載 SpringApplication 運行監聽器,並監聽 Spring Boot 事件
  • 建立 Spring 應用上下文和建立 Environment
public ConfigurableApplicationContext run(String... args) {
    //記錄運行時間
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
        //Spring 應用的上下文
    ConfigurableApplicationContext context = null;
        //記錄啓動期間的錯誤
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //配置文件加載及優先級判斷
    configureHeadlessProperty();
        //獲取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
        //加載運行監聽器
    listeners.starting();
    try {
            //建立ApplicationArguments對象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
            //加載屬性配置
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
        configureIgnoreBeanInfo(environment);
            //打印Banner
        Banner printedBanner = printBanner(environment);
            //建立應用上下文
        context = createApplicationContext();
            //實例化SpringBootExceptionReporter用於報告啓動過程錯誤。
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
            //初始化應用上下文
        prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
            //刷新應用上下文(IOC容器的準備,初始化Bean)
        refreshContext(context);
            //應用上下刷新完成以後
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
            //啓動日誌記錄器
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
        }
            //啓動運行監聽器
        listeners.started(context);
            //啓動後須要的操做
        callRunners(context, applicationArguments);
        ....
    }
}

Step1. 加載SpringApplication運行監聽器

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

利用 Spirng 的工廠加載機制,實例化 SpringApplicationRunListeners 實現類,並排序集合。具體實現方法以下:

  1. 經過 SpringFactoriesLoader.loadFactoryNames 來掃描 META-INF/spring.factories 下符合 SpringApplicationRunListeners 類型的資源名稱。
  2. 實例化全部在 META-INF/spring.factories 下找到的資源信息
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListenr

因而可知就只有 EventPublishingRunListenr 一個實現類

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;
        //實例化SimpleApplicationEventMulticaster事件發佈者
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        //以迭代的方法逐一進行ApplicationListener的監聽
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }

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

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        for (ApplicationListener<?> listener : this.application.getListeners()) {
            if (listener instanceof ApplicationContextAware) {
                ((ApplicationContextAware) listener).setApplicationContext(context);
            }
            context.addApplicationListener(listener);
        }
        this.initialMulticaster.multicastEvent(
                new ApplicationPreparedEvent(this.application, this.args, context));
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        context.publishEvent(
                new ApplicationStartedEvent(this.application, this.args, context));
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        context.publishEvent(
                new ApplicationReadyEvent(this.application, this.args, context));
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        ApplicationFailedEvent event = new ApplicationFailedEvent(this.application,
                this.args, context, exception);
        if (context != null && context.isActive()) {
            // Listeners have been registered to the application context so we should
            // use it at this point if we can
            context.publishEvent(event);
        }
        else {
            // An inactive context may not have a multicaster so we use our multicaster to
            // call all of the context's listeners instead
            if (context instanceof AbstractApplicationContext) {
                for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
                        .getApplicationListeners()) {
                    this.initialMulticaster.addApplicationListener(listener);
                }
            }
            this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
            this.initialMulticaster.multicastEvent(event);
        }
    }

    private static class LoggingErrorHandler implements ErrorHandler {

        private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);

        @Override
        public void handleError(Throwable throwable) {
            logger.warn("Error calling ApplicationEventListener", throwable);
        }

    }

}

EventPublishingRunListener 實例化的時候,會實例化一個 SimpleApplicationEventMulticaster 事件發佈者(它的做用就是監聽容器中發佈的事件,只要事件發生,就觸發監聽器的回調,來完成事件驅動開發),因而接下來調用 listeners.starting() 方法就會經過其內部的 initialMulticaster 屬性發布 ApplicationStartingEvent 事件。

Step2. 建立Spirng應用上下文

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                //初始化階段的推斷Web類型
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_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);
    }

根據初始化階段的推斷Web應用類型來建立對應的 ConfigurableApplicationContext 實例

若是推斷的爲 SERVLETWeb 類型就實例化這個對象

web.servlet.context.AnnotationConfigServletWebServerApplicationContext

Step3. 建立 Environment

private ConfigurableEnvironment prepareEnvironment(
    SpringApplicationRunListeners listeners,
    ApplicationArguments applicationArguments) {
        // Create and configure the environment
        //建立 ConfigurableEnvironment 對象
    ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置 ConfigurableEnvironment
    configureEnvironment(environment, applicationArguments.getSourceArgs());
        //發佈 ApplicationEnvironmentPreparedEvent 事件
    listeners.environmentPrepared(environment);
        //將 ConfigurableEnvironment 綁定到 SpringApplication 中
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
        .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

根據初始化階段的推斷Web應用類型來建立對應的 ConfigurableEnvironment 實例。

至此整一個的 SpringBoot 過程已經分析完畢:咱們來總結一下:

  1. 首先初始化 SpringApplication 類,並推斷 WEB 啓動類型,再初始化和實現應用事件監聽器,而後推斷引導類。
  2. 經過 SpringFactoriesLoader 加載的 SpringApplicationRunListener,調用它們的 started 方法。
  3. 根據 Web 服務類型建立不一樣的 Spring 應用上下文,並將以前準備好的 Environment 設置給 Spring 應用上下文 ApplicationContext 使用。
  4. 建立並配置當前 Spring Boot 應用將要使用的 Environment,如 applocation.properties 文件和外部配置。
  5. SpirngBoot 開始啓動。

推薦閱讀

java | 什麼是動態代理?

SpringBoot | 是如何實現日誌的?

SpringBoot | 是如何實現自動配置的?

後語

若是本文對你哪怕有一丁點幫助,請幫忙點好看。你的好看是我堅持寫做的動力。
另外,關注以後在發送 1024 可領取免費學習資料。

資料詳情請看這篇舊文:Python、C++、Java、Linux、Go、前端、算法資料分享

一個優秀的廢人

相關文章
相關標籤/搜索