從 SpringApplication 認識 Spring 應用啓動過程

一、SpringApplication 是什麼?

Spring 應用的啓動類。

二、SpringApplication 執行了什麼?

  • 創建 ApplicationContext 實例

    ApplicationContext 就是我們所說的容器實例。

  • 註冊 CommandLinePropertySource

    CommandLinePropertySource 的作用是將命令行參數輸出爲 Spring 屬性。

  • 刷新 ApplicationContext

    這一步驟包括諸多操作,並且會加載所有的單例 bean。

  • 觸發 CommandLineRunner bean

    CommandLineRunner 是一個接口,它只有一個 run() 方法。

    凡是實現了此接口的類,如果被加載進容器,就會執行其 run() 方法。

    容器中可以包含多個實現 CommandLineRunner 的 bean,執行順序可以遵從 Ordered 接口或者 @Order 註解設置。

三、bean 加載源

SpringApplication 有諸多 bean 加載源:

  • AnnotatedBeanDefinitionReader

    顧名思義,註解 bean 定義讀取。

  • XmlBeanDefinitionReader

    xml 配置資源讀取。

  • ClassPathBeanDefinitionScanner

    classpath 路徑掃描。

  • GroovyBeanDefinitionReader

    ... ...

四、SpringApplication 創建

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();
	this.bootstrapRegistryInitializers = new ArrayList<>(
			getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

1、resourceLoader

參數可以爲 null,爲 null 時,使用默認:

(this.resourceLoader != null) ? this.resourceLoader: new DefaultResourceLoader(null);

2、primarySources

主要的 bean 定義來源。

3、webApplicationType

web 應用類型判斷:

  • NONE:應用不會以 web 應用運行,且不會啓動內嵌 web 服務器。

  • SERVLET:基於 servlet web 應用,運行於內嵌 web 服務器。

  • REACTIVE:響應式 web 應用,運行於內嵌 web 服務器。

4、bootstrapRegistryInitializers

BootstrapRegistryInitializer:回調接口,用於 BootstrapRegistry 初始化。

BootstrapRegistry:對象註冊器,作用期間爲從應用啓動,Environment 處理直到 ApplicationContext 完備。

5、setInitializers

ApplicationContextInitializer 列表設置。

ApplicationContextInitializer:回調接口,用於 Spring ConfigurableApplicationContext 初始化。

通常用於 web 應用 ApplicationContext 自定義初始化。如註冊 property source、激活 profile 等。

6、setListeners

ApplicationListener 列表設置。

ApplicationListener:應用事件監聽接口,基於標準的 EventListener 接口,觀察者模式實現。

7、mainApplicationClass

main class

五、SpringApplication.run() 邏輯

	public ConfigurableApplicationContext run(String... args) {
		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
			listeners.started(context, timeTakenToStartup);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}
		try {
			Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
			listeners.ready(context, timeTakenToReady);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

創建,刷新 ApplicationContext 並運行 Spring 應用。

1、startTime

使用 System.nanoTime(),計算耗時間隔更精確。不可用於獲取具體時刻。

2、創建啓動上下文

DefaultBootstrapContext = createBootstrapContext();

BootstrapContext:啓動上下文,生命週期同 BootstrapRegistry。

DefaultBootstrapContext 繼承了 BootstrapContext、BootstrapRegistry。

用於 BootstrapRegistry 初始化。

3、ConfigurableApplicationContext

可配置的 ApplicationContext。

4、獲取事件監聽器

SpringApplicationRunListeners = getRunListeners()。

Spring 應用運行期間事件監聽。

5、starting 事件

listeners.starting():starting step。

6、啓動參數處理

ApplicationArguments:提供 SpringApplication 啓動參數訪問。

7、應用環境配置

ConfigurableEnvironment = prepareEnvironment()

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
  • configureEnvironment() 模板方法,代理執行 configurePropertySources() 及 configureProfiles() 方法。

    configurePropertySources():PropertySource 配置,用於添加、移除或者調序 PropertySource 資源。CommandLinePropertySource 在這一步處理。

    configureProfiles():應用 profile 設置。

  • ConfigurationPropertySources.attach(environment)

    ConfigurationPropertySources:提供對 ConfigurationPropertySource 的訪問。

    attach(environment):就是將這個功能提供給 environment。

  • listeners.environmentPrepared(bootstrapContext, environment)

    environment-prepared step。

  • DefaultPropertiesPropertySource.moveToEnd(environment)

    DefaultPropertiesPropertySource:是一個 MapPropertySource,包含 SpringApplication 可以使用的一些默認屬性。爲了使用方便,默認會置於尾序。

  • bindToSpringApplication(environment)

    將 environment 綁定到 SpringApplication。

    Binder:用於對象綁定的容器。

8、configureIgnoreBeanInfo()

9、打印 Banner

printBanner()。

10、創建 ApplicationContext

createApplicationContext()。

內部通過 ApplicationContextFactory 創建。

ApplicationContextFactory:策略接口,默認實現爲 DefaultApplicationContextFactory。

11、ApplicationStartup 設置

爲容器設置 ApplicationStartup,用於記錄啓動過程性能指標。

12、ApplicationContext 準備

prepareContext()

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		bootstrapContext.close(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		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());
		}
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		// 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);
	}
  • 設置環境

  • postProcessApplicationContext() 前置處理

    beanNameGenerator 設置,用於 bean 名稱生成。

    resourceLoader 設置,用於資源加載。

    addConversionService:ConversionService 類型轉換 Service。

  • applyInitializers()

    ApplicationContextInitializer 應用

  • contextPrepared 事件

    【spring.boot.application.context-prepared】step

  • BootstrapContext 關閉

  • 註冊 springApplicationArguments bean

  • 註冊 springBootBanner bean

  • AbstractAutowireCapableBeanFactory

    設置是否允許 bean 之間的循環依賴,並自動處理,默認爲 true。

    設置是否允許 bean 定義覆蓋,默認爲 true。

  • lazyInitialization 懶加載

    設置 LazyInitializationBeanFactoryPostProcessor post-processor。

  • PropertySource 重排序

    設置 PropertySourceOrderingBeanFactoryPostProcessor post-processor。

  • getAllSources() bean 定義源加載

  • load() bean 定義加載,BeanDefinitionLoader

    用於從底層資源加載 bean 定義信息,包括 xml、JavaConfig。

    是基於 AnnotatedBeanDefinitionReader、XmlBeanDefinitionReader、ClassPathBeanDefinitionScanner 的門面模式。

    beanNameGenerator、resourceLoader、environment 設置。

    資源加載:

    private void load(Object source) {
      	Assert.notNull(source, "Source must not be null");
      	if (source instanceof Class<?>) {
      		load((Class<?>) source);
      		return;
      	}
      	if (source instanceof Resource) {
      		load((Resource) source);
      		return;
      	}
      	if (source instanceof Package) {
      		load((Package) source);
      		return;
      	}
      	if (source instanceof CharSequence) {
      		load((CharSequence) source);
      		return;
      	}
      	throw new IllegalArgumentException("Invalid source type " + source.getClass());
      }
    
  • contextLoaded() contextLoaded 事件

    【spring.boot.application.context-loaded】step。

13、ApplicationContext 刷新

refreshContext()

註冊 shutdownHook。

Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));

刷新操作:加載或刷新

AbstractApplicationContext::refresh()

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
				contextRefresh.end();
			}
		}
	}

作爲啓動方法,如果失敗,則必須銷燬所有已創建的單例bean。

  • StartupStep【spring.context.refresh】

  • 準備刷新 prepareRefresh()

    設置啓動日期。

    設置 active 標誌。

    initPropertySources():子類實現 PropertySource 初始化。

    validateRequiredProperties():校驗 ConfigurablePropertyResolver#setRequiredProperties 設置的必需屬性。

    obtainFreshBeanFactory():通過子類獲取最新的內部 bean factory。如果存在舊的則先銷燬,然後再創建新的返回。

  • prepareBeanFactory() 準備 bean factory

    setBeanClassLoader():默認爲線程上下文類加載器,用於 bean 定義加載。

    setBeanExpressionResolver() spel 表達式解析設置:StandardBeanExpressionResolver。

    addPropertyEditorRegistrar():ResourceEditorRegistrar 用於 bean 創建過程。

    添加 ApplicationContextAwareProcessor post-processor。

    註冊依賴:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext。

    添加 ApplicationListenerDetector post-processor:用於檢測發現實現了 ApplicationListener 的 bean。

    LoadTimeWeaver 處理。

    environment、systemProperties、systemEnvironment、applicationStartup 註冊。

  • postProcessBeanFactory():用於子類實現,修改內部 bean factory。

    這一時期,所有的 bean 定義都已被加載,但還未實例化。

  • StartupStep【spring.context.beans.post-process】

  • invokeBeanFactoryPostProcessors() 觸發所有已註冊的 BeanFactoryPostProcessor

  • registerBeanPostProcessors() 註冊 bean post-processor

  • StartupStep【spring.context.beans.post-process】 結束

  • initMessageSource() MessageSource 初始化

    容器內 bean 名稱:messageSource。

    存在則檢查並設置 ParentMessageSource。

    不存在則創建默認 DelegatingMessageSource,設置 ParentMessageSource 並註冊。

  • initApplicationEventMulticaster() 事件分發初始化

    容器 bean:applicationEventMulticaster。ApplicationEventMulticaster 接口,用於管理 ApplicationListener,並執行事件分發。

    不存在則創建並註冊 SimpleApplicationEventMulticaster 對象。

  • onRefresh()

    用於子類初始化一些特有的 bean。

    模板方法,用於重寫實現刷新邏輯。

  • registerListeners() 監聽器註冊

    將實現了 ApplicationListener 接口的 bean 註冊到容器。

  • finishBeanFactoryInitialization() 實例化所有餘下的單例 bean。

    conversionService。

    註冊內嵌值(${...})解析器。

    初始化 LoadTimeWeaverAware。

    停用類型匹配 ClassLoader。

    freezeConfiguration() 凍結所有的 bean 定義。所有註冊的 bean 定義都不允許再有變更。

    preInstantiateSingletons() 實例化所有餘下的單例 bean。

14、afterRefresh()

ApplicationContext 刷新完畢後調用。

15、StartupInfoLogger

記錄應用啓動信息。

16、started 事件

listeners.started()

17、Runner 調用

包括 ApplicationRunner 和 CommandLineRunner。

18 ready 事件

listeners.ready()

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