Springboot 啓動流程源碼主流程分析

Springboot 啓動流程源碼主流程分析

一 、 前半部分new SpringApplication(primarySources)

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}
  1. WebApplicationType.deduceFromClasspath(); 是根據所有加載的類中,是否存在下面的幾個類,判斷是什麼類型的應用,設置到SpringApplication類的webApplicationType屬性上,後面創建容器的時候(二中的6createApplicationContext() ),根據它來判斷該創建創建那種類型的容器。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
      "org.springframework.web.context.ConfigurableWebApplicationContext" };

private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
      + "web.servlet.DispatcherServlet";

private static final String WEBFLUX_INDICATOR_CLASS = "org."
      + "springframework.web.reactive.DispatcherHandler";

private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";

static WebApplicationType deduceFromClasspath() {
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
   return WebApplicationType.SERVLET;
}
  1. 設置初始化的類,從META-INF/spring.factories中獲取ApplicationContextInitializer指定的所有類,設置到SpringApplication的initializers屬性上。
setInitializers((Collection) getSpringFactoriesInstances(
         ApplicationContextInitializer.class));

這個裏面getSpringFactoriesInstances 會獲取META-INF/spring.factories 文件中指定的類,是獲取所有當前應用中,jar包目錄中有META-INF/spring.factories文件,而不是某一個jar包下的。下圖是我調試時候的截圖。後續還會調用很多次這種getSpringFactoriesInstances方法,都從MultiValueMap類型的緩存中取,只在第一次的時候從文件中讀取,之後就緩存了。
在這裏插入圖片描述
3. 和2類似, 這次取得是ApplicationListener指定的類。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  1. 根拒線程堆棧,找到main方法所在的類,主要用來打日誌。
this.mainApplicationClass = deduceMainApplicationClass();

二 、接下來看看後半部分的run方法。

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
  this.configureHeadlessProperty();
  SpringApplicationRunListeners listeners = this.getRunListeners(args);
  listeners.starting();

  Collection exceptionReporters;
  try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
    this.configureIgnoreBeanInfo(environment);
    Banner printedBanner = this.printBanner(environment);
    context = this.createApplicationContext();
    exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
    this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
    this.refreshContext(context);
    this.afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
      (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
    }

    listeners.started(context);
    this.callRunners(context, applicationArguments);
  } catch (Throwable var10) {
    this.handleRunFailure(context, var10, exceptionReporters, listeners);
    throw new IllegalStateException(var10);
  }

  try {
    listeners.running(context);
    return context;
  } catch (Throwable var9) {
    this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
    throw new IllegalStateException(var9);
  }
}
  1. 計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
stopWatch.stop();

stopWatch是一個計時器,可以統計start到stop之間所用的時間。

  1. 設置系統屬性java.awt.headless
this.configureHeadlessProperty();

這一句是表示運行在服務器端,在沒有顯示器和鼠標鍵盤的模式下工作,模擬輸入輸出設備功能

  1. 加載容器啓動監聽器
SpringApplicationRunListeners listeners=this.getRunListeners(args);
listeners.starting();

這兩句的作用是:

  • 從META-INF/spring.factories 配置中獲取org.springframework.boot.SpringApplicationRunListener指定的類,只有一個org.springframework.boot.context.event.EventPublishingRunListener。加載並實例化
  • 加載並實例化以後,調用他們的starting()方法。這個方法裏面發佈了一個ApplicationStartingEvent()事件。這個事件有四個支持的監聽者,如下圖
    在這裏插入圖片描述
    ​ 依次調用每個監聽者的onApplicationEvent()方法,例如LoggingApplicationListener的onApplicationEvent() 方法就是判斷一下是那種日誌系統,log4j、logback等等,然後執行一些日誌系統初始化的工作。
  1. 獲取配置
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                         applicationArguments);
configureIgnoreBeanInfo(environment);

這幾句的作用是獲取系統的環境變量、系統屬性、還有我們自己的application.properties文件中的配置,下圖是調試時候的截圖,可以看到application.properties中的配置就是在prepareEnvironment(listeners, applicationArguments);這一步讀取進來的。
在這裏插入圖片描述
同時發佈ApplicationEnvironmentPreparedEvent事件,調用每個監聽者的監聽方法。
在這裏插入圖片描述

  1. 這個打印logo
Banner printedBanner = printBanner(environment);
  1. 獲取ConfigurableApplicationContext,就是根據1中設置的容器類型,創建容器類。
context = createApplicationContext();
  1. 加載org.springframework.boot.diagnostics.FailureAnalyzers 指定的類。這些類主要是在項目啓動失敗之後,處理異常,打印日誌等,他是在啓動異常的catch塊中調用的:
exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
catch (Throwable ex) {
   handleRunFailure(context, ex, exceptionReporters, listeners);
   throw new IllegalStateException(ex);
}
  1. 準備容器。 這一步主要是在容器刷新之前的準備動作:把之前解析好的環境environment、容器監聽器listeners、啓動時的參數applicationArguments等屬性設置到容器對象。其中包含一個非常關鍵的操作:將啓動類注入容器,爲後續開啓自動化配置奠定基礎。 容器能加載到啓動類,就能解析到啓動類上的註解,就能逐個的加載要掃描的類,包括各種配置類及他們的初始化工作。
prepareContext(context, environment, listeners, applicationArguments,
      printedBanner);
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//設置容器環境,包括各種變量
		context.setEnvironment(environment);
		//執行容器後置處理
		postProcessApplicationContext(context);
		//執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
		applyInitializers(context);
		//發送容器已經準備好的事件,通知各監聽器
		listeners.contextPrepared(context);
		//打印log
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//註冊啓動參數bean,這裏將容器指定的參數封裝成bean,注入容器
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		//設置banner
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}
		// 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. 刷新容器。 spring的 ioc 和 aop的功能就在這裏實現。比較複雜。後面再來。
refreshContext(context);
  1. 刷新容器後的擴展接口, 設計模式中的模板方法,默認爲空實現。如果有自定義需求,可以重寫該方法。比如打印一些啓動結束log,或者一些其它後置處理。
afterRefresh(context, applicationArguments);
  1. 發佈容器啓動結束事件,有兩個監聽者,但是什麼事情都沒有做。
listeners.started(context);
  1. 執行所有實現了ApplicationRunner、CommandLineRunner接口的類中的run方法,經常用來自定義在容器啓動啓動完成後立即要做的事情。
callRunners(context, applicationArguments);
  1. 繼續發佈org.springframework.boot.context.event.ApplicationReadyEvent事件,調用該事件的監聽器的監聽方法。
try {
    listeners.running(context);
}

有兩個作者我覺得寫得比較好的,我也參考了許多:
https://blog.csdn.net/qq_26000415/column/info/18708 寫得很詳細
https://blog.csdn.net/woshilijiuyi/article/details/82219585 寫得很精髓

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章