SpringBoot應用啓動過程簡單分析

今天看了Springboot的啓動過程,自己分析了一下(有什麼不足請指出)。
** 本文主要是 2.0.2.RELEASE 的源碼 **
SpringBoot項目通過SpringApplication.run(App.class, args)來啓動:

@SpringBootApplication
public class App {
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

接下來,通過源碼來看看SpringApplication.run()方法的執行過程。
1、 調用SpringApplication類的靜態方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

2、SpringApplication對象初始化

public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	// 拿到資源加載器
	this.resourceLoader = resourceLoader;
	// 通過“斷言” 判斷 primarySources 是否爲空
	Assert.notNull(primarySources, "PrimarySources must not be null");
	// 使用Set確保唯一性以防止重複
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// 判斷是否爲WEB環境
	this.webApplicationType = deduceWebApplicationType();
	// 找到META-INF/spring.factories中ApplicationContextInitializer所有實現類,並將其實例化
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	// 找到META-INF/spring.factories中ApplicationListener所有實現類,並將其實例化
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// 獲取當前main方法類對象,即測試類中的App實例
	this.mainApplicationClass = deduceMainApplicationClass();
}

對象初始化過程中,使用到了getSpringFactoriesInstances方法:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
	// 獲取 當前線程類加載器
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// 使用Set確保唯一性以防止重複
	// Use names and ensure unique to protect against duplicates
	// 讀取META-INF/spring.factories指定接口的實現類
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
	List<T> instances = new ArrayList<>(names.size());
	for (String name : names) {
		try {
			Class<?> instanceClass = ClassUtils.forName(name, classLoader);
			Assert.isAssignable(type, instanceClass);
			Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
			T instance = (T) BeanUtils.instantiateClass(constructor, args);
			instances.add(instance);
		} catch (Throwable ex) {
			throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
		}
	}
	return instances;
}

3、通過setInitializers來完成, ApplicationContextInitializer應用程序初始化器,做一些初始化的工作:

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
	this.initializers = new ArrayList<>();
	this.initializers.addAll(initializers);
}
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
	// 實例化
	void initialize(C applicationContext);
}

4、通過setListeners來完成, ApplicationListener應用程序初始化器,做一些初始化的工作:

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
	this.listeners = new ArrayList<>();
	this.listeners.addAll(listeners);
}
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
	void onApplicationEvent(E event);
}

這裏的應用程序事件(ApplicationEvent)有應用程序啓動事件(ApplicationStartedEvent),失敗事件(ApplicationFailedEvent),準備事件(ApplicationPreparedEvent)等。

應用程序事件監聽器跟監聽事件是綁定的。比如ConfigServerBootstrapApplicationListener只跟ApplicationEnvironmentPreparedEvent事件綁定,LiquibaseServiceLocatorApplicationListener只跟ApplicationStartedEvent事件綁定,LoggingApplicationListener跟所有事件綁定等。

默認情況下,initialize方法從spring.factories文件中找出的key爲ApplicationContextInitializer的類有:

org.springframework.boot.context.config.DelegatingApplicationContextInitializer
org.springframework.boot.context.ContextIdApplicationContextInitializer
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

key爲ApplicationListener的有:

org.springframework.boot.context.config.ConfigFileApplicationListener
org.springframework.boot.context.config.AnsiOutputApplicationListener
org.springframework.boot.logging.LoggingApplicationListener
org.springframework.boot.logging.ClasspathLoggingApplicationListener
org.springframework.boot.autoconfigure.BackgroundPreinitializer
org.springframework.boot.context.config.DelegatingApplicationListener
org.springframework.boot.builder.ParentContextCloserApplicationListener
org.springframework.boot.context.FileEncodingApplicationListener
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

SpringApplication的執行
分析run方法之前,先看一下SpringApplication中的一些事件和監聽器概念。
首先是SpringApplicationRunListeners類和SpringApplicationRunListener類的介紹。
SpringApplicationRunListeners內部持有SpringApplicationRunListener集合和1個Log日誌類。用於SpringApplicationRunListener監聽器的批量執行。
SpringApplicationRunListener看名字也知道用於監聽SpringApplication的run方法的執行。

它定義了5個步驟:

  1. started(run方法執行的時候立馬執行;對應事件的類型是ApplicationStartedEvent)
  2. environmentPrepared(ApplicationContext創建之前並且環境信息準備好的時候調用;對應事件的類型是ApplicationEnvironmentPreparedEvent)
  3. contextPrepared(ApplicationContext創建好並且在source加載之前調用一次;沒有具體的對應事件)
  4. contextLoaded(ApplicationContext創建並加載之後並在refresh之前調用;對應事件的類型是ApplicationPreparedEvent)
  5. finished(run方法結束之前調用;對應事件的類型是ApplicationReadyEvent或ApplicationFailedEvent)

SpringApplicationRunListener目前只有一個實現類EventPublishingRunListener,它把監聽的過程封裝成了SpringApplicationEvent事件並讓內部屬性(屬性名爲multicaster)ApplicationEventMulticaster接口的實現類SimpleApplicationEventMulticaster廣播出去,廣播出去的事件對象會被SpringApplication中的listeners屬性進行處理。

所以說SpringApplicationRunListener和ApplicationListener之間的關係是通過ApplicationEventMulticaster廣播出去的SpringApplicationEvent所聯繫起來的。

SpringApplicationRunListener

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch(); // 構造一個任務執行觀察器
		stopWatch.start();// 開始執行,記錄開始時間
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 設置headless系統變量  
		configureHeadlessProperty();
		// 啓動SpringApplicationRunListener監聽器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 啓動所有的監聽
		listeners.starting();
		try {
			// 實例化ApplicationArguments對象 構造一個應用程序參數持有類
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			// 配置environment		
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			// 配置 IgnoreBeanInfo
			configureIgnoreBeanInfo(environment);
			// 生成 Banner
			Banner printedBanner = printBanner(environment);
			// 創建Spring容器
			context = createApplicationContext();
			// 加載spring.factories中的SpringBootExceptionReporter實現類
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			// 創建、裝置、刷新、運行ApplicationContext
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			// 加載或刷新持久化形式的配置(如xml文件、properties文件,和數據庫信息)。
			refreshContext(context);
			// 容器創建完成之後執行額外一些操作
			afterRefresh(context, applicationArguments);
			stopWatch.stop();// 執行結束,記錄執行時間
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			// 廣播出ApplicationReadyEvent事件給相應的監聽器執行
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners); // 這個過程報錯的話會執行一些異常操作、然後廣播出ApplicationFailedEvent事件給相應的監聽器執行
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;// 返回Spring容器
	}

通過對getRunListeners的跟蹤,你會發現SpringApplicationRunListener是這樣的

public interface SpringApplicationRunListener {
	/**
	 * 通知監聽器,SpringBoot開始啓動
	 */
	void started();
	/**
	 * 通知監聽器,環境配置完成
	 */
	void environmentPrepared(ConfigurableEnvironment environment);
	/**
	 * 通知監聽器,ApplicationContext已創建並初始化完成
	 */
	void contextPrepared(ConfigurableApplicationContext context);
	/**
	 * 通知監聽器,ApplicationContext已完成IOC配置
	 */
	void contextLoaded(ConfigurableApplicationContext context);
	/**
	 * 通知監聽器,SpringBoot開始完畢
	 */
	void finished(ConfigurableApplicationContext context, Throwable exception);
}

附圖爲ApplicationListener監聽接口實現類,每個類對應了一種事件。
ApplicationListener

解讀prepareEnvironment

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		// 創建並配置 environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 配置一些環境信息。比如profile,命令行參數, 配置 environment 的 PropertySources 和 Profiles
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		// 廣播出ApplicationEnvironmentPreparedEvent事件給相應的監聽器執行
		listeners.environmentPrepared(environment);
		// 將environment綁定到SpringApplication上
		bindToSpringApplication(environment);
		// 根據webApplicationType創建一個ConfigurableEnvironment對象
		// 如果webApplicationType 是 WebApplicationType.SERVLET 或 WebApplicationType.REACTIVE 則創建其子類StandardServletEnvironment()對象,否則創建StandardEnvironment對象
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
		// 在environment.propertySources中添加(如果沒有的話)一個
		// ConfigurationPropertySourcesPropertySource對象,
		// 使得environment管理的PropertySource對象能適配 PropertySourcesPropertyResolver,
		// 能夠通過屬性名get到具體的配置
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

Spring容器的創建createApplicationContext方法如下:

protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		// 如果是web程序,那麼構造org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext容器
		// 如果是reactive ,那麼構造 boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext容器
        // 否則構造org.springframework.context.annotation.AnnotationConfigApplicationContext容器
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

解讀prepareContext

	private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment); // 設置Spring容器的環境信息
		postProcessApplicationContext(context); // 回調方法,Spring容器創建之後做一些額外的事
	    applyInitializers(context); // SpringApplication的的初始化器開始工作
		// 遍歷調用SpringApplicationRunListener的contextPrepared方法。
		// 目前只是將這個事件廣播器註冊到Spring容器中
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}

		// Add boot specific singleton beans
		// 把應用程序參數持有類註冊到Spring容器中,並且是一個單例
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}

		// Load the sources
		// 將beans載入到ApplicationContext容器中
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		// 通知監聽器,beans載入ApplicationContext完畢
		listeners.contextLoaded(context);
	}

##總結##
spring boot通過擴展了jar協議,抽象出Archive概念,和配套的JarFile,JarUrlConnection,LaunchedURLClassLoader,從而實現了上層應用無感知的all in one的開發體驗。儘管Executable war並不是spring提出的概念,但spring boot讓它發揚光大。

  1. SpringBoot啓動的時候,不論調用什麼方法,都會構造一個SpringApplication的實例,然後調用這個實例的run方法,這樣就表示啓動SpringBoot。
  2. 在run方法調用之前,也就是構造SpringApplication的時候會進行初始化的工作,初始化的時候會做以下幾件事:
    把參數sources設置到SpringApplication屬性中,這個sources可以是任何類型的參數

判斷是否是web程序,並設置到webEnvironment這個boolean屬性中
找出所有的初始化器,默認有5個,設置到initializers屬性中
找出所有的應用程序監聽器,默認有9個,設置到listeners屬性中
找出運行的主類(main class)
SpringApplication構造完成之後調用run方法,啓動SpringApplication,run方法執行的時候會做以下幾件事:

3.構造一個StopWatch,觀察SpringApplication的執行
找出所有的SpringApplicationRunListener並封裝到SpringApplicationRunListeners中,用於監聽run方法的執行。監聽的過程中會封裝成事件並廣播出去讓初始化過程中產生的應用程序監聽器進行監聽
構造Spring容器(ApplicationContext),並返回
3.1 創建Spring容器的判斷是否是web環境,是的話構造AnnotationConfigEmbeddedWebApplicationContext,否則構造AnnotationConfigApplicationContext
3.2 初始化過程中產生的初始化器在這個時候開始工作
3.3 Spring容器的刷新(完成bean的解析、各種processor接口的執行、條件註解的解析等等)
從Spring容器中找出ApplicationRunner和CommandLineRunner接口的實現類並排序後依次執行
啓動過程框圖

spring boot是一個驚人的項目,可以說是spring的第二春,spring-cloud-config, spring-session, metrics, remote shell等都是深愛開發者喜愛的項目、特性。幾乎可以肯定設計者是有豐富的一線開發經驗,深知開發人員的痛點。

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