首页 > 其他分享 >从 SpringApplication 认识 Spring 应用启动过程

从 SpringApplication 认识 Spring 应用启动过程

时间:2023-06-15 12:23:20浏览次数:46  
标签:启动 Spring listeners SpringApplication environment source bean context new

一、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()

标签:启动,Spring,listeners,SpringApplication,environment,source,bean,context,new
From: https://www.cnblogs.com/niejunlei/p/17479268.html

相关文章

  • 解惑 spring 嵌套事务
    解惑spring嵌套事务/***@author王政*@date2006-11-24*/在所有使用spring的应用中,声明式事务管理可能是使用率最高的功能了,但是,从我观察到的情况看,绝大多数人并不能深刻理解事务声明中不同事务传播属性配置的的含义,让我们来看一下Trans......
  • 解析Spring内置作用域及其在实践中的应用
    摘要:本文详细解析了Spring的内置作用域,包括Singleton、Prototype、Request、Session、Application和WebSocket作用域,并通过实例讲解了它们在实际开发中的应用。本文分享自华为云社区《Spring高手之路4——深度解析Spring内置作用域及其在实践中的应用》,作者:砖业洋__。本文详细解析......
  • 详解spring事务属性
    Spring声明式事务让我们从复杂的事务处理中得到解脱。使得我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。再也无需要我们在与事务相关的方法中处理大量的try…catch…finally代码。我们在使用Spring声明式事务时,有一个非常重要的概念就是事务属性。事务......
  • 最近学习spring声明式事务小结
    在一些需要同时操作几个更新的业务逻辑中,而这几个更新又是互相关联的,也就是说我们希望它成功就一起成功,失败就一起失败。这样我们就需要使用事务。关于如何配置spring声明式事务就不说了,需要的人可以在网上找下,肯定会配置一个类似下面的bean<beanid="cao"class......
  • 使用Spring Boot和H2完全工作的原型
    我们在Spring中使用了很多H2,特别是用于单元测试。但是,我们可能希望拥有一个包含数据的全功能原型,而不是单元测试。H2是完美的候选者。它适用于Spring,与大多数数据库具有很强的语法兼容性,并提供用于检查数据的UI。想象一下面试任务的情景。您希望您的示例开箱即用,尽可能少的配置为审......
  • Mongodb - org.springframework.dao.DuplicateKeyException
    首先明确场景为mongodb,此异常在进行mongodb的插入操作时抛出,插入的主键已经存在。衍生场景,使用upsert时抛出,此处的包括了$set和$setOnInsert由于upsert非原子操作,如果在多线程环境下:线程A和线程B同时对数据库未存在的记录record1进行upsert,有可能会出现两个线程都判断为应该进行......
  • SpringBoot+druid+dynamic+clickhouse+mysql实现读写分离
    背景:clickhouse+mysql实现读写分离1.配置mysql2.安装clickhouse3.使用可视化工具连接clickhouse4.创建clickhouse的数据库并连接mysql5.SpringBoot+druid+dynamic配置多数据源实现读写分离一.背景由于系统数据量过大,查询条件自定义过多,mysql在查询时响应太慢,所以使用cli......
  • 顶奢好文:3W字,穿透Spring事务原理、源码,至少读10遍
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • springboot启动流程 (1) 流程概览
    本文将通过阅读源码方式分析SpringBoot应用的启动流程,不涉及Spring启动部分(有相应的文章介绍)。本文不会对各个流程做展开分析,后续会有文章介绍详细流程。SpringApplication类应用启动入口使用以下方式启动一个SpringBoot应用:@SpringBootApplicationpublicclassSpringBoo......
  • SpringBoot集成支付宝 - 少走弯路就看这篇
    最近在做一个网站,后端采用了SpringBoot,需要集成支付宝进行线上支付,在这个过程中研究了大量支付宝的集成资料,也走了一些弯路,现在总结出来,相信你读完也能轻松集成支付宝支付。在开始集成支付宝支付之前,我们需要准备一个支付宝商家账户,如果是个人开发者,可以通过注册公司或者让有公......