【Spring boot】啟動過程原始碼分析

2022-10-06 06:00:15

啟動過程結論

  • 推測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的原始碼。可以在公眾號下方選單點選檢視之前的文章!
  • 這個公眾號的所有技術點,會分析的很深入!
  • 這個公眾號,無廣告!!!