SpringBoot是如何動起來的

襄垣發表於2019-02-22

程式入口

SpringApplication.run(BeautyApplication.class, args);
複製程式碼

執行此方法來載入整個SpringBoot的環境。

1. 從哪兒開始?

SpringApplication.java

	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		//...
  }
複製程式碼

呼叫SpringApplication.java 中的 run 方法,目的是載入Spring Application,同時返回 ApplicationContext

2. 執行了什麼?

2.1 計時

記錄整個Spring Application的載入時間!

StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ...
stopWatch.stop();
if (this.logStartupInfo) {
	new StartupInfoLogger(this.mainApplicationClass)
			.logStarted(getApplicationLog(), stopWatch);
}
複製程式碼

2.2 宣告

// 宣告 ApplicationContext
ConfigurableApplicationContext context = null;
// 宣告 一個異常報告集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
複製程式碼

2.3 指定程式執行模式

指定 java.awt.headless,預設是true 一般是在程式開始啟用headless模式,告訴程式,現在你要工作在Headless mode下,就不要指望硬體幫忙了,你得自力更生,依靠系統的計算能力模擬出這些特性來。

private void configureHeadlessProperty() {
	System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
			SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
複製程式碼

2.4 配置監聽併發布應用啟動事件

SpringApplicationRunListener 負責載入 ApplicationListener事件。

SpringApplicationRunListeners listeners = getRunListeners(args);
// 開始
listeners.starting();
// 處理所有 property sources 配置和 profiles 配置,準備環境,分為標準 Servlet 環境和標準環境
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
// 準備應用上下文
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
// 完成
listeners.started(context);
// 異常
handleRunFailure(context, ex, exceptionReporters, listeners);
// 執行
listeners.running(context);
複製程式碼

getRunListeners 中根據 type = SpringApplicationRunListener.class 去拿到了所有的 Listener 並根據優先順序排序。 對應的就是 META-INF/spring.factories 檔案中的 org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener

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;
	}
複製程式碼

ApplicationListener 中 , 可以針對任何一個階段插入處理程式碼

public interface SpringApplicationRunListener {

	/**
	 * Called immediately when the run method has first started. Can be used for very
	 * early initialization.
	 */
	void starting();

	/**
	 * Called once the environment has been prepared, but before the
	 * {@link ApplicationContext} has been created.
	 * @param environment the environment
	 */
	void environmentPrepared(ConfigurableEnvironment environment);

	/**
	 * Called once the {@link ApplicationContext} has been created and prepared, but
	 * before sources have been loaded.
	 * @param context the application context
	 */
	void contextPrepared(ConfigurableApplicationContext context);

	/**
	 * Called once the application context has been loaded but before it has been
	 * refreshed.
	 * @param context the application context
	 */
	void contextLoaded(ConfigurableApplicationContext context);

	/**
	 * The context has been refreshed and the application has started but
	 * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
	 * ApplicationRunners} have not been called.
	 * @param context the application context.
	 * @since 2.0.0
	 */
	void started(ConfigurableApplicationContext context);

	/**
	 * Called immediately before the run method finishes, when the application context has
	 * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
	 * {@link ApplicationRunner ApplicationRunners} have been called.
	 * @param context the application context.
	 * @since 2.0.0
	 */
	void running(ConfigurableApplicationContext context);

	/**
	 * Called when a failure occurs when running the application.
	 * @param context the application context or {@code null} if a failure occurred before
	 * the context was created
	 * @param exception the failure
	 * @since 2.0.0
	 */
	void failed(ConfigurableApplicationContext context, Throwable exception);

}
複製程式碼

3. 每個階段執行的內容

3.1 listeners.starting();

在載入Spring Application之前執行,所有資源和環境未被載入。

3.2 prepareEnvironment(listeners, applicationArguments);

建立 ConfigurableEnvironment; 將配置的環境繫結到Spring Application中;

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
複製程式碼

3.3 prepareContext

配置忽略的Bean;

	private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
		if (System.getProperty(
				CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
			Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
					Boolean.class, Boolean.TRUE);
			System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
					ignore.toString());
		}
	}
複製程式碼

列印日誌-載入的資源

Banner printedBanner = printBanner(environment);
複製程式碼

根據不同的WebApplicationType建立Context

context = createApplicationContext();
複製程式碼

3.4 refreshContext

支援定製重新整理

	/**
	 * Register a shutdown hook with the JVM runtime, closing this context
	 * on JVM shutdown unless it has already been closed at that time.
	 * <p>This method can be called multiple times. Only one shutdown hook
	 * (at max) will be registered for each context instance.
	 * @see java.lang.Runtime#addShutdownHook
	 * @see #close()
	 */
	void registerShutdownHook();
複製程式碼

3.5 afterRefresh

重新整理後的實現方法暫未實現

	/**
	 * Called after the context has been refreshed.
	 * @param context the application context
	 * @param args the application arguments
	 */
	protected void afterRefresh(ConfigurableApplicationContext context,
			ApplicationArguments args) {
	}
複製程式碼

3.6 listeners.started(context);

到此為止, Spring Application的環境和資源都載入完畢了; 釋出應用上下文啟動完成事件; 執行所有 Runner 執行器 - 執行所有 ApplicationRunner 和 CommandLineRunner 這兩種執行器

// 啟動
callRunners(context, applicationArguments);
複製程式碼

3.7 listeners.running(context);

觸發所有 SpringApplicationRunListener 監聽器的 running 事件方法

相關文章