【Spring boot】啟動過程源碼分析

啟動過程結論

  • 推測web應用類型。
  • spi的方式獲取BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationContextInitializer對象。
  • 通過調用棧推測出main()方法所在的類。
  • 調用啟動方法:run(String... args)。後面的步驟在這個方法內部!
  • 觸發SpringApplicationRunListener的starting()。
  • 創建Environment對象。
  • 觸發SpringApplicationRunListener的environmentPrepared()。
  • 列印Banner。
  • 創建Spring容器對象(ApplicationContext)。
  • 利用ApplicationContextInitializer初始化Spring容器對象。
  • 觸發SpringApplicationRunListener的contextPrepared()。
  • 調用DefaultBootstrapContext對象的close()。
  • 將啟動類作為配置類註冊到Spring容器中(load()方法)。
  • 觸發SpringApplicationRunListener的contextLoaded()。
  • 刷新Spring容器。
  • 觸發SpringApplicationRunListener的started()。
  • 調用ApplicationRunner和CommandLineRunner。
  • 觸發SpringApplicationRunListener的ready()。
  • 上述過程拋異常了就觸發SpringApplicationRunListener的failed()。

入口位置

  • 第一步構造對象:new SpringApplication(primarySources)
  • 第二步調用SpringApplication.run(String... args)
// 我們自己寫的main方法
@SpringBootApplication
public class ZfcqApp {
    public static void main(String[] args) {
        SpringApplication.run(ZfcqApp.class, args);
    }
}

/**
 * 他會調用SpringApplication的run方法
 * primarySource:我們傳入的類的class
 * args:我們傳入的參數,一般是啟動的時候-D制定的參數
 */
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

/**
 * 繼續看內部調用的run方法
 */
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 倆步,第一步構造對象:new SpringApplication(primarySources)
    // 第二步調用SpringApplication.run(String... args)
    // 這裡我們可以在main方法中分開倆步去寫,從而可以在中間設置SpringApplication對象的資訊
    return new SpringApplication(primarySources).run(args);
}

構造SpringApplication源碼分析

/**
 * 調用倆個參數的構造方法
 * primarySources:我們主類的class
 */
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

/**
 * 倆個產生的構造方法
 */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 賦值到全局,這裡第一次傳入的是null
    this.resourceLoader = resourceLoader;
    // 主類存在的判斷
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 賦值到全局,這裡一般傳入的是我們的main方法的類
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    // 推測WEB應用的類型(NONE、SERVLET、REACTIVE)
    this.webApplicationType = WebApplicationType.deduceFromClasspath();

    // 從spring.factories中獲取BootstrapRegistryInitializer對象的值
    this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

    // 從spring.factories中獲取ApplicationContextInitializer對象的值
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // 從spring.factories中獲取ApplicationListener對象的值。非常重要的有一個是EnvironmentPostProcessorApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 推測出main方法所在的類!SpringApplication.run(ZfcqApp.class, args);可以傳任意的類!
    this.mainApplicationClass = deduceMainApplicationClass();
}

推測web應用類型

  • REACTIVE:web應用。
  • NONE:無Servlet,不是web應用。
  • SERVLET:除去上面倆種的其他應用。
static WebApplicationType deduceFromClasspath() {
    // 如果項目依賴中存在org.springframework.web.reactive.DispatcherHandler,並且不存在org.springframework.web.servlet.DispatcherServlet,那麼應用類型為WebApplicationType.REACTIVE
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    // 如果項目依賴中不存在org.springframework.web.reactive.Dispatche#rHandler,也不存在org.springframework.web.servlet.DispatcherServlet,那麼應用類型為WebApplicationType.NONE
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    // 否則,應用類型為WebApplicationType.SERVLET
    return WebApplicationType.SERVLET;
}

推測出Main類(main()方法所在的類)

  • 從調用棧中去獲取main類!
private Class<?> deduceMainApplicationClass() {
    try {
        // 獲取調用棧數組
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        // 找到調用棧中的main方法!
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

核心的啟動方法:run(String... args)

public ConfigurableApplicationContext run(String... args) {
    // 開始時間
    long startTime = System.nanoTime();

    // 創建引導啟動器,類似一個ApplicationContext,可以往裡面添加一些對象。
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();

    // spring容器對象,開始的時候為空
    ConfigurableApplicationContext context = null;
    // 與awt有關,一般用不上
    configureHeadlessProperty();

    // spring boot的啟動監聽器
    // 從spring.factories中獲取SpringApplicationRunListener對象的值
    // 默認會拿到EventPublishingRunListener,他會啟動各個地方的ApplicationEvent事件。
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 發布開始啟動的事件:ApplicationstartingEvent
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        // 把run方法的參數進行封裝
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 準備environment對象:包括作業系統、jvm、ymal、properties....配置
        // 發布一個ApplicationEnvironmentPreparedEvent事件。
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

        // 默認spring.beaninfo.ignore為true,表示不需要jdk去快取BeanInfo資訊,spring自己快取。
        // 這裡是spring在創建bean的時候會利用jdk的一些工具來解析一個類的相關資訊,jdk在解析一個類的資訊的時候會進行快取,這裡就是禁止了jdk的快取。
        configureIgnoreBeanInfo(environment);

        // 列印Banner
        Banner printedBanner = printBanner(environment);

        // 根據應用類型,創建spring容器
        context = createApplicationContext();
        // jdk9的一個機制,默認沒做任何操作
        context.setApplicationStartup(this.applicationStartup);

        // 準備容器的操作
        // 利用ApplicationContextInitializer初始化spring容器
        // 發布ApplicationContextInitializedEvent(容器初始化完成)事件
        // 發布BootstrapContextClosedEvent(關閉引導容器)事件
        // 註冊primarySources類(run方法存進來的配置類)
        // 發布ApplicationPreparedEvent事件
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        // refresh容器:解析配置類、掃描、啟動webServer。spring相關的邏輯!
        refreshContext(context);

        // 空方法,類似spring的onRefresh方法,可以由子類實現。
        afterRefresh(context, applicationArguments);

        // 啟動的事件
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }

        // 發布一個ApplicationStartedEvent事件,表示spring已經啟動完成
        listeners.started(context, timeTakenToStartup);

        // 從spring容器中獲取ApplicationRunner和CommandLineRunner,並執行他的run方法。
        // 這倆個可以自己定義Bean
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 失敗之後,發布一個失敗的事件:ApplicationFailedEvent
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        // 計算下事件
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);

        // 一切都成功,發布ApplicationStartedEvent事件
        listeners.ready(context, timeTakenToReady);
    }
    catch (Throwable ex) {
        // 失敗之後,發布一個失敗的事件:ApplicationFailedEvent
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

創建引導啟動器

private DefaultBootstrapContext createBootstrapContext() {
    // 構建一個DefaultBootstrapContext對象,這個對象是2.4.0之後才會有!
    DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
    // 利用bootstrapRegistryInitializers初始化bootstrapContext。可以在spring.factories中設置bootstrapRegistryInitializers
    this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
    return bootstrapContext;
}

準備environment

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    // Create and configure the environment   這句話是源碼中本身的注釋!
    // 創建ApplicationServletEnvironment。
    ConfigurableEnvironment environment = getOrCreateEnvironment();

    // 添加SimpleCommandLinePropertySource放在首位
    configureEnvironment(environment, applicationArguments.getSourceArgs());

    // 把所有的PropertySource封裝為ConfigurationPropertySourcesPropertySource,然後添加到environment中,放在首位!
    ConfigurationPropertySources.attach(environment);

    // 發布ApplicationEnvironmentPreparedEvent(應用Environment準備完成)事件,表示環境已經準備好了。默認EnvironmentPostProcessorApplicationListener去處理這個事件!
    listeners.environmentPrepared(bootstrapContext, environment);

    // 把DefaultProperties放到最後
    DefaultPropertiesPropertySource.moveToEnd(environment);

    // 環境中spring.main.environment-prefix參數校驗
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");

    // 環境中spring.main參數校驗
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = convertEnvironment(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

準備spring容器的操作

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 設置環境變數到spring容器
    context.setEnvironment(environment);

    // 設置在SpringApplication上的BeanNameGenerator、resourceLoader設置到spring容器中
    postProcessApplicationContext(context);

    // 使用ApplicationContextInitializer初始化spring容器
    applyInitializers(context);

    // 容器初始化完成,發布ApplicationContextInitializedEvent事件
    listeners.contextPrepared(context);

    // 關閉引導的容器
    bootstrapContext.close(context);

    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 註冊一些單例Bean
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }

    // spring容器設置AllowCircularReferences和allowBeanDefinitionOverriding
    if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    // 拿到啟動配置(run方法傳遞進來的)
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 將啟動配置類解析為BeanDefinition註冊到spring容器
    load(context, sources.toArray(new Object[0]));
    // 發布ApplicationPreparedEvent事件,表示已經啟動好spring容器
    listeners.contextLoaded(context);
}

結束語

  • 你的點贊是我提高文章品質最大的動力!!!
  • 獲取更多本文的前置知識文章,以及新的有價值的文章,讓我們一起成為架構師!
  • 目前已經完成了並發編程、MySQL、spring源碼、Mybatis的源碼。可以在公眾號下方菜單點擊查看之前的文章!
  • 這個公眾號的所有技術點,會分析的很深入!
  • 這個公眾號,無廣告!!!
    作者公众号.jpg