SpringBoot(一)啟動相關【死磕原始碼】

請叫我王蜀黍發表於2018-07-18

上篇提到了啟動過程中的一些事件,這篇就來翻原始碼來了解下這些事件,廢話不多少,整起。。

SpringApplication.run(new Object[]{
         WebApiThirdpartyApplication.class
    }, args);
複製程式碼

每個springboot專案啟動都是從這開始,通過呼叫SpringApplication.run()啟動,接下來看看run都做了啥

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
	return new SpringApplication(sources).run(args);
}
複製程式碼

例項SpringApplication,通過構造器去呼叫初始化方法

public SpringApplication(Object... sources) {
	initialize(sources);
}
複製程式碼

上篇說事件的時候提到過 一個事件是在監聽註冊和初始化自後執行的,除了這兩個操作在任何處理之前

private void initialize(Object[] sources) {
	if (sources != null && sources.length > 0) {
		this.sources.addAll(Arrays.asList(sources));
	}
	this.webEnvironment = deduceWebEnvironment();
	setInitializers((Collection) getSpringFactoriesInstances(
		ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼

接下來看下 這個初始化做了幾件事

  1. this.webEnvironment = deduceWebEnvironment();,判斷應用上下問環境
 private boolean deduceWebEnvironment() {
	for (String className : WEB_ENVIRONMENT_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return false;
		}
	}
	return true;
}
複製程式碼

其中WEB_ENVIRONMENT_CLASSES是一個靜態常量陣列:

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
         "org.springframework.web.context.ConfigurableWebApplicationContext" };
複製程式碼

可以看到是根據org.springframework.util.ClassUtils的靜態方法去判斷classpath裡面是否有WEB_ENVIRONMENT_CLASSES包含的類,如果有都包含則返回true則表示啟動一個WEB應用,否則返回false啟動一個標準Spring的應用。

  1. setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));

這個方法則是初始化classpath下的所有的可用的ApplicationContextInitializer。 3. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 這個方法則是初使化classpath下的所有的可用的ApplicationListener。 4. this.mainApplicationClass = deduceMainApplicationClass();,進入方法看下

private Class<?> deduceMainApplicationClass() {
	try {
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}
複製程式碼

最後找出main方法的全類名並返回其例項並設定到SpringApplicationthis.mainApplicationClass完成初始化。然後呼叫SpringApplication例項的run方法來啟動應用。程式碼如下:

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.started();
   try {
       ApplicationArguments applicationArguments = new DefaultApplicationArguments(
              args);
       ConfigurableEnvironment environment = prepareEnvironment(listeners,
              applicationArguments);
       Banner printedBanner = printBanner(environment);
       context = createApplicationContext();
       analyzers = new FailureAnalyzers(context);
       prepareContext(context, environment, listeners, applicationArguments,
              printedBanner);
       refreshContext(context);
       afterRefresh(context, applicationArguments);
       listeners.finished(context, null);
       stopWatch.stop();
       if (this.logStartupInfo) {
          new StartupInfoLogger(this.mainApplicationClass)
                 .logStarted(getApplicationLog(), stopWatch);
       }
       return context;
   }
   catch (Throwable ex) {
       handleRunFailure(context, listeners, analyzers, ex);
       throw new IllegalStateException(ex);
   }
}
複製程式碼

這裡我們看以下程式碼:

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
複製程式碼

首先是獲取啟動時傳入引數args並初始化為ApplicationArguments物件,SpringApplication.run(Application.class, args);取這裡傳入值。 然後配置SpringBoot應用的環境:

ConfigurableEnvironment environment = prepareEnvironment(listeners,
                  applicationArguments);
複製程式碼

Banner printedBanner = printBanner(environment); 列印標誌這個方法不說明,因為沒有什麼實質性作用,如果想玩玩修改一下標誌,那可以在專案的classpath下新建一個banner.txt檔案。

然後下面程式碼就是比較核心的:

context = createApplicationContext();
   analyzers = new FailureAnalyzers(context);
   prepareContext(context, environment, listeners, applicationArguments,
          printedBanner);
   refreshContext(context);
   afterRefresh(context, applicationArguments);
複製程式碼

首先是createApplicationContext()方法:

protected ConfigurableApplicationContext createApplicationContext() {
       Class<?> contextClass = this.applicationContextClass;
       if (contextClass == null) {
           try {
              contextClass = Class.forName(this.webEnvironment
                     ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
           }
           catch (ClassNotFoundException ex) {
              thrownew IllegalStateException(
                     "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                     ex);
           }
       }
       return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
    }

複製程式碼

可以看出根據之前初始化過程初始化的this.webEnvironment來決定初始化一個什麼容器。如果classpath下是否有javax.servlet.Servlet和 org.springframework.web.context.ConfigurableWebApplicationContext類, 則使用DEFAULT_WEB_CONTEXT_CLASS初始化容器,如果不存在則用DEFAULT_CONTEXT_CLASS初始化容器。

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";
 
    /**
     * The class name of application context that will be used by default for web
     * environments.
     */
    publicstaticfinal String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
複製程式碼

以上是程式碼指定了容器的類名,最後通過Spring的工具類初始化容器類bean BeanUtils.instantiate(contextClass);

完成容器的建立工作。然後執行以下的幾個步驟完成整個容器的建立與啟動以及bean的注入功能。

prepareContext(context, environment, listeners, applicationArguments,
                  printedBanner);
複製程式碼

以下這一句程式碼是實現spring-boot-starter-*的自動化配置的關鍵。

refreshContext(context); afterRefresh(context, applicationArguments);

至此通過SpringBoot啟動的容器已經構造完成。這裡忽略了啟動流程中的收集各種Listener,建立Environment及Environment的初始化的。

下篇準備整理下自動配置,敬請關注

相關文章