SpringApplication运行阶段

2023-11-28 22:32

本文主要是介绍SpringApplication运行阶段,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

SpringApplication运行阶段围绕run(String …)方法展开,该过程结合初始化阶段完成的状态进一步完善了运行时所需要准备的资源,随后启动Spring应用上下文,在此期间伴随Spring Boot和Spring事件的触发,形成完整的SpringApplication生命周期:

  • SpringApplication准备阶段
  • SpringApplication启动阶段
  • SpringApplication启动后阶段

1、SpringApplication准备阶段

本阶段涉及的范围从run(String …)方法调用开始,到refreshContext(ConfigurableApplicationContext)调用前:

	public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);Banner printedBanner = printBanner(environment);context = createApplicationContext();exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);prepareContext(context, environment, listeners, applicationArguments, printedBanner);refreshContext(context);...}...}

该过程依次准备的核心对象为:SpringApplicationRunListeners、ApplicationArguments、ConfigurableEnvironment、Banner、ConfigurableApplicationContext和SpringBootExceptionReporter集合,接下来逐一讨论。

1.1、理解SpringApplicationRunListeners

SpringApplicationRunListeners是由getRunListeners(args)方法创建的:

	private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}

其中SpringApplicationRunListeners属于组合模式的实现,内部关联了SpringApplicationRunListener的集合:

class SpringApplicationRunListeners {private final Log log;private final List<SpringApplicationRunListener> listeners;SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {this.log = log;this.listeners = new ArrayList<>(listeners);}void starting() {for (SpringApplicationRunListener listener : this.listeners) {listener.starting();}}...
}

SpringApplicationRunListeners结构简单,内部一系列方法实现均依赖于SpringApplicationRunListener集合。

1.2、理解SpringApplicationRunListener

SpringApplicationRunListener可理解为Spring Boot应用的运行时监听器,其监听方法被SpringApplicationRunListeners迭代地执行,如上面的starting()方法,其他方法还包括:

监听方法运行阶段说明SpringBoot起始版本
starting()Spring Boot应用刚启动1.0
environmentPrepared(ConfigurableEnvironment)ConfigurableEnvironment准备妥当,允许将其调整1.0
contextPrepared(ConfigurableApplicationContext)ConfigurableApplicationContext准备妥当,允许将其调整1.0
contextLoaded(ConfigurableApplicationContext)ConfigurableApplicationContext已装载,但仍未启动1.0
started(ConfigurableApplicationContext context)ConfigurableApplicationContext已启动,此时Spring Bean已初始化完成2.0
running(ConfigurableApplicationContext)Spring应用正在运行2.0
failed(ConfigurableApplicationContext, Throwable)Spring应用运行失败2.0

SpringApplicationRunListener集合则来自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 = getClassLoader();// Use names and ensure unique to protect against duplicatesSet<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;}

SpringApplicationRunListener的构造参数必须依次为SpringApplication和String[]类型,回顾getRunListeners方法:

	private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}

同时结合SpringFactoriesLoader机制,Spring Boot SpringApplicationRunListener内建实现如下:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener作为Spring Boot唯一的内建实现,完全符合上述构造器参数签名的约束:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {private final SpringApplication application;private final String[] args;private final SimpleApplicationEventMulticaster initialMulticaster;public EventPublishingRunListener(SpringApplication application, String[] args) {this.application = application;this.args = args;this.initialMulticaster = new SimpleApplicationEventMulticaster();for (ApplicationListener<?> listener : application.getListeners()) {this.initialMulticaster.addApplicationListener(listener);}}@Overridepublic int getOrder() {return 0;}@Overridepublic void starting() {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}...
}

该构造器将参数application和args均与属性关联,并且将根据SpringApplication已关联的ApplicationListener实例列表动态地添加到SimpleApplicationEventMulticaster对象中。SimpleApplicationEventMulticaster源于Spring Framework,实现ApplicationEventMulticaster接口用于发布Spring应用事件。因此EventPublishingRunListener实际上充当Spring Boot事件发布者的角色,如在starting()方法中发布ApplicationStartingEvent。

1.3、理解Spring Boot事件

尽管SpringApplicationRunListener和Spring Boot事件(SpringApplicationEvent)从Spring Boot1.0开始引入,然而贯穿Spring Boot1.x~2.x的发展,监听方法与Spring Boot事件的对应关系也发生了变化:

监听方法Spring Boot事件SpringBoot起始版本
starting()ApplicationStartingEvent1.0
environmentPrepared(ConfigurableEnvironment)ApplicationEnvironmentPreparedEvent1.0
contextPrepared(ConfigurableApplicationContext)ApplicationContextInitializedEvent1.0
contextLoaded(ConfigurableApplicationContext)ApplicationPreparedEvent1.0
started(ConfigurableApplicationContext context)ApplicationStartedEvent2.0
running(ConfigurableApplicationContext)ApplicationReadyEvent2.0
failed(ConfigurableApplicationContext, Throwable)ApplicationFailedEvent2.0

SpringApplicationRunListener是Spring Boot应用运行时监听器,并非Spring Boot事件监听器,以上Spring Boot事件所对应的ApplicationListener实现是由SpringApplication构造器参数关联并添加到属性SimpleApplicationEventMulticaster中的。比如SpringApplicationRunListener.starting()方法运行后,ApplicationStartingEvent 随即触发,此时initialMulticaster同步地执行ApplicationListener<ApplicationStartingEvent >集合的监听回调方法onApplicationEvent(ApplicationStartingEvent),这些行为保证均源于Spring Framework事件/监听器机制。对于Spring Boot应用而言,Spring Boot事件和Spring Framework事件是存在差异的:除了通常的Spring Framework事件(比如ContextRefreshedEvent)之外,SpringApplication还发送一些附加的应用程序事件。Spring Boot应用程序运行时,应用程序事件按以下顺序发送:

  1. ApplicationStartingEvent在运行开始时但在任何处理之前发送,侦听器和初始化器的注册除外。
  2. 当上下文中要使用的环境已知但在创建上下文之前,将发送ApplicationEnvironmentPreparedEvent。
  3. 在准备ApplicationContext并且调用ApplicationContextInitializers时,但在加载任何bean定义之前,发送ApplicationContextInitializedEvent。
  4. ApplicationPreparedEvent在刷新开始之前发送,但在加载bean定义之后发送。
  5. ApplicationStartedEvent在刷新上下文之后、调用任何应用程序和命令行运行程序之前发送。
  6. AvailabilityChangeEvent紧接着以LivenessState.CORRECT发送,以指示应用程序被视为活动的。
  7. 在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent。
  8. AvailabilityChangeEvent在with ReadinessState.ACCEPTING\u通信之后立即发送,以指示应用程序已准备好为请求提供服务。
  9. 如果启动时出现异常,则会发送ApplicationFailedEvent。

上面的列表只包括绑定到SpringApplication的SpringApplicationEvents。除此之外,还将在ApplicationPreparedEvent之后和ApplicationStartedEvent之前发布以下事件:

  • WebServerInitializeEvent在WebServer准备就绪后发送。ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent分别是servlet和reactive变体。
  • 在刷新ApplicationContext时发送ContextRefreshedEvent。

Spring Framework事件是由Spring应用上下文ApplicationContext对象触发的。然而Spring Boot事件的发布者则是SpringApplication.initialMulticaster属性(SimpleApplicationEventMulticaster类型),并且SimpleApplicationEventMulticaster也来自Spring Framework,那么Spring Boot事件与Spring Framework事件必然存在某种联系,同时两者也存在差异。

1.4、理解Spring Boot事件/监听机制

Spring Boot事件/监听机制同样基于ApplicationEventMulticaster、ApplicationEvent和ApplicationListener实现。Spring Boot事件/监听机制既利用了Spring事件/监听API又和Spring应用上下文事件发布和监听器管理属于“各自为政,互不干涉”的关系。

SpringApplicationRunListener生命周期方法contextLoaded(ConfigurableApplicationContext)将所关联的ApplicationListener实例列表添加到当前Spring应用上下文ConfigurableApplicationContext对象中:

	@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {for (ApplicationListener<?> listener : this.application.getListeners()) {if (listener instanceof ApplicationContextAware) {((ApplicationContextAware) listener).setApplicationContext(context);}context.addApplicationListener(listener);}this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));}

根据SpringApplicationRunListener生命周期回调的特性,此时Spring应用上下文尚未初始化,因此以上添加操作最终会追加到AbstractApplicationContext所关联的SimpleApplicationEventMulticaster 属性中,当Spring应用上下文发布Spring事件后,这些被contextLoaded方法添加的ApplicationListener集合能够将他们监听。

SpringApplication从META-INF/spring.factories资源中加载的ApplicationListener实例列表关联到Spring应用上下文ConfigurableApplicationContext对象,SpringApplication中的ApplicationListener能够监听ConfigurableApplicationContext 所发送的事件。

尽管Spring Boot ApplicationListener能够监听Spring事件,然而他绝大多数的事件场景在监听Spring Boot事件方面。

Spring Boot内建事件监听器

在Spring Boot场景中,无论是Spring事件监听器还是Spring Boot事件监听器,均配置在META-INF/spring.factories资源中,并以org.springframework.context.ApplicationListener作为属性名称,属性值为ApplicationListener实现类。其ApplicationListener配置声明分布在spring-boot和spring-boot-autoconfigure.jar中:
org.springframework.boot:spring-boot:2.3.0

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

org.springframework.boot:spring-boot-autoconfigure:2.3.0

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
ApplicationListener实现监听事件场景说明引入版本
ClearCachesApplicationListenerContextRefreshedEvent清除ReflectionUtils和ClassLoader缓存1.4
ParentContextCloserApplicationListenerParentContextAvailableEvent如果父上下文对象关闭,则关闭应用程序上下文的侦听器。它侦听刷新事件并从中获取当前上下文,然后侦听关闭的事件并将其传播到层次结构中1.0
CloudFoundryVcapEnvironmentPostProcessorApplicationPreparedEvent从延迟日志记录切换到立即日志记录到指定的目标1.3
FileEncodingApplicationListenerApplicationEnvironmentPreparedEvent检测spring.mandatory-file-encoding属性是否与系统属性file.encoding匹配1.0
AnsiOutputApplicationListenerApplicationEnvironmentPreparedEvent生成ANSI编码的输出,自动尝试检测终端是否支持ANSI1.2
ConfigFileApplicationListenerApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent通过从已知文件位置加载属性来配置上下文环境。默认情况下,属性将从“application.properties”和/或“application.yml”文件加载1.0
DelegatingApplicationListenerApplicationEnvironmentPreparedEvent将Spring事件委派给配置context.listener.classes所指定的多个ApplicationListener实现类1.0
ClasspathLoggingApplicationListenerApplicationEnvironmentPreparedEvent、ApplicationFailedEventDEBUG级别日志记录当前用的Class Path1.0
LoggingApplicationListenerApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent、ContextClosedEvent、ApplicationFailedEvent识别日志框架并加载日志配置文件1.0
LiquibaseServiceLocatorApplicationListenerApplicationStartingEvent取代qiquibase ServiceLocator1.0
BackgroundPreinitializerApplicationStartingEvent、ApplicationReadyEvent、ApplicationFailedEventApplicationListener在后台线程中触发耗时任务的早期初始化。1.3

以上最重要的Spring Boot内建事件监听器莫过于ConfigFileApplicationListener 和LoggingApplicationListener ,前者负责Spring Boot应用配置属性文件的加载,后者用于Spring Boot日志系统的初始化(日志框架识别、日志配置文件加载等)。

1.5、装配ApplicationArguments

当执行SpringApplicationRunListener.starting()方法后,SpringApplication运行进入装配ApplicationArguments逻辑:

	public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);...}...}

ApplicationArguments实例的创建源于Spring Boot1.3其实现类为DefaultApplicationArguments,一个用于简化Spring Boot应用启动参数的封装接口,它的底层实现基于Spring Framework中的命令行配置源SimpleCommandLinePropertySource:

public class DefaultApplicationArguments implements ApplicationArguments {private final Source source;private final String[] args;public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;}...private static class Source extends SimpleCommandLinePropertySource {...}}

SimpleCommandLinePropertySource 将命令行参数分为两组,一为“选项参数”,二为“非选项参数”,两者均依赖SimpleCommandLineArgsParser解析。其中合法的选项选项的前缀必须是“–”,并且可以指定值,也可以不指定值。如果指定了一个值,则名称和值必须用等号(“=”)分隔,不能有空格。

Valid examples of option arguments
–foo
–foo=bar
–foo=“bar then baz”
–foo=bar,baz,biz
Invalid examples of option arguments
-foo
–foo bar
–foo = bar
–foo=bar --foo=baz --foo=biz

反之非选择项参数则未包含“–”前缀,并通过getNonOptionArgs()方法提供。
当ApplicationArguments实例在SpringApplication的准备阶段构造完毕后,它将投入ApplicationRunner回调方法参数的运用:

@FunctionalInterface
public interface ApplicationRunner {void run(ApplicationArguments args) throws Exception;
}

当ApplicationArguments 实例准备完毕后,SpringApplication的执行操作进入准备ConfigurableEnvironment的阶段。

1.6、准备ConfigurableEnvironment

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);listeners.environmentPrepared(environment);bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}ConfigurationPropertySources.attach(environment);return environment;}

getOrCreateEnvironment()方法根据webApplicationType创建不同的Environment:

	private ConfigurableEnvironment getOrCreateEnvironment() {if (this.environment != null) {return this.environment;}switch (this.webApplicationType) {case SERVLET:return new StandardServletEnvironment();case REACTIVE:return new StandardReactiveWebEnvironment();default:return new StandardEnvironment();}}

configureEnvironment用来配置environment ,包括设置ApplicationConversionService、添加defaultProperties、添加命令行参数、添加additionalProfiles。

	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {if (this.addConversionService) {ConversionService conversionService = ApplicationConversionService.getSharedInstance();environment.setConversionService((ConfigurableConversionService) conversionService);}configurePropertySources(environment, args);configureProfiles(environment, args);}

1.7、创建Spring应用上下文(ConfigurableApplicationContext)

SpringApplication通过createApplicationContext()方法创建Spring应用上下文,实际上Spring应用上下文才是驱动整体Spring Boot应用组件的核心引擎:

	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET:contextClass = Class.forName(DEFAULT_SERVLET_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);}

根据webApplicationType创建Spring应用上下文,或通过setApplicationContextClass(Class<? extends ConfigurableApplicationContext>)方法设置的Class创建Spring应用上下文。

1.8、Spring应用上下文运行前准备

Spring应用上下文运行前的准备工作由SpringApplication.prepareContext方法完成,根据SpringApplicationRunListener的生命周期回调又分为"Spring应用上下文准备阶段"和“Spring应用上下文装载阶段”。

1.8.1、Spring应用上下文准备阶段

本阶段的执行从prepareContext方法开始,到SpringApplicationRunListeners#contextPrepared截止:

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {context.setEnvironment(environment);postProcessApplicationContext(context);applyInitializers(context);listeners.contextPrepared(context);...}

该过程由设置Spring应用上下文ConfigurableEnvironment 、Spring应用上下文后置处理、运用Spring应用上下文初始化器和Spring应用上下文已准备生命周期回调组成。

1.8.1.1、设置Spring应用上下文ConfigurableEnvironment

本过程仅执行一行语句context.setEnvironment(environment);Spring应用上下文ConfigurableApplicationContext不仅通过其关联的BeanFactory对象管理Bean及它们的生命周期,而且属性也关联ConfigurableEnvironment实例。在Spring Framework中,该接口有两种实现:StandardEnvironment和StandardServletEnvironment,然而在Spring Boot2.0中,新增了StandardReactiveWebEnvironment的实现。默认情况下,ConfigurableEnvironment属性由模板方法AbstractApplicationContext.createEnvironment()创建,因此当Spring应用上下文类型不同时,该方法返回的对象类型也是不同的。例如以上类型的实例分别由AbstractApplicationContext、AbstractRefreshableWebApplicationContext和AnnotationConfigReactiveWebApplicationContext创建。无论哪种具体类型的ConfigurableEnvironment对象,其功能特性与getOrCreateEnvironment()方法返回值并无明显差异。

当Spring应用上下文类型为AnnotationConfigReactiveWebServerApplicationContext时,其ConfigurableEnvironment属性类型为StandardReactiveWebEnvironment,StandardReactiveWebEnvironment只是简单地继承StandardEnvironment:

public class StandardReactiveWebEnvironment extends StandardEnvironment implements ConfigurableReactiveWebEnvironment {}

所以StandardReactiveWebEnvironment 等于StandardEnvironment ,所以SpringApplication.getOrCreateEnvironment()方法返回值适用于以上三种不同的应用类型。既然AbstractApplicationContext.createEnvironment()与SpringApplication.getOrCreateEnvironment()的返回结果基本相同,为何SpringApplication要提前设置呢?其根本原因在于AbstractApplicationContext.createEnvironment()方法的执行时机。该方法的执行调用链路为refresh()->prepareRefresh()->getEnvironment()->createEnvironment():

	public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();...}...}protected void prepareRefresh() {...getEnvironment().validateRequiredProperties();...}@Overridepublic ConfigurableEnvironment getEnvironment() {if (this.environment == null) {this.environment = createEnvironment();}return this.environment;}

createEnvironment()方法的执行不仅需要environment属性从未初始化,并且依赖于Spring上下文启动(refresh())的生命周期。如果environment属性在此时创建,则其配置属性源(PropertySource)的装载极有可能通过BeanFactoryPostProcessor实现完成,然而在众多BeanFactoryPostProcessor集合中,程序很难保证该负责装载的BeanFactoryPostProcessor实现以最高优先级执行。因此ConfigurableEnvironment 对象的装配工作需在refresh()方法调用前完成。
按照prepareContext方法的执行顺序,下一步执行Spring应用上下文后置处理。

1.8.1.2、Spring应用上下文后置处理

Spring应用上下文后置处理是根据SpringApplication#(ConfigurableApplicationContext)方法的命名而来的:

	protected void postProcessApplicationContext(ConfigurableApplicationContext context) {if (this.beanNameGenerator != null) {context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,this.beanNameGenerator);}if (this.resourceLoader != null) {if (context instanceof GenericApplicationContext) {((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);}if (context instanceof DefaultResourceLoader) {((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());}}if (this.addConversionService) {context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());}}

postProcessApplicationContext方法覆盖当前Spring应用上下文默认所关联的ResourceLoader和ClassLoader。不过在设置ResourceLoader对象时,其前提条件时参数context是否为GenericApplicationContext,前面讨论的两种不同Spring Boot应用上下文类型AnnotationConfigReactiveWebApplicationContext和AnnotationConfigApplicationContext均为GenericApplicationContext的子类。
在这里插入图片描述
除postProcessApplicationContext方法外,applyInitializers方法也能扩展ConfigurableApplicationContext实例。

1.8.1.3、运用Spring应用上下文初始化器(ApplicationContextInitializer)

SpringApplication构造阶段所加载的Spring应用上下文初始化器存放在SpringApplication实例的listeners字段,该字段是ApplicationContextInitializer列表。在Spring应用上下文准备阶段时,它用于初始化ConfigurableApplicationContext实例:

	protected void applyInitializers(ConfigurableApplicationContext context) {for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");initializer.initialize(context);}}public Set<ApplicationContextInitializer<?>> getInitializers() {return asUnmodifiableOrderedSet(this.initializers);}private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {List<E> list = new ArrayList<>(elements);list.sort(AnnotationAwareOrderComparator.INSTANCE);return new LinkedHashSet<>(list);}

当applyInitializers方法执行时,首先通过getInitializers方法获取Set<ApplicationContextInitializer>,该Set将原有属性initializers List排序并去重。如果属性initializers 中的实例完全来自SpringApplication构造器,那么List已经排过序了,如此看来asUnmodifiableOrderedSet方法中的排序似乎没有存在的必要。其实不然,该方法还需要考虑initializers 属性中来自addInitializers方法的成员,随后将initializers 属性添加至LinkedHashSet对象后返回,其目的在于不希望同一ApplicationContextInitializer初始化多次,不过无法保证ApplicationContextInitializer的实现类覆盖hashCode和equals方法。当ApplicationContextInitializer实现类未覆盖hashCode和equals方法时,多个同类实例重复添加到SpringApplication中,其asUnmodifiableOrderedSet方法无法去重,同理当同一ApplicationContextInitializer实现类在不同META-INF/spring.factories资源中声明时,重复执行的情况也会出现,因为SpringFactoriesLoader#loadFactoryNames方法也未去重。

凡是使用Spring工厂加载机制的场景建议被加载实现类覆盖hashCode和equals方法,以免重复执行所带来的隐患。

由于applyInitializers(ConfigurableApplicationContext)方法迭代地执行ApplicationContextInitializer集合,所以对他们的顺序性和重复性应予以高度的关注。既然ApplicationContextInitializer提供初始化ConfigurableApplicationContext的能力,在职责上自然与postProcessApplicationContext(ConfigurableApplicationContext)方法在某种程度上重叠。尽管该方法确保优先于ApplicationContextInitializer执行,然而这并不能保证该方法调整后的ConfigurableApplicationContext实例不被后续ApplicationContextInitializer对象覆盖性修改。即使在Spring Cloud场景中,也没有出现扩展SpringApplication类的情况,因此ApplicationContextInitializerpostProcessApplicationContext方法被覆盖的概率基本为零。所以建议开发人员以扩展接口的方式实现ConfigurableApplicationContext的初始化。

ApplicationContextInitializer接口在Spring Framework时代并未得到广泛关注,原因在于ApplicationContextInitializer仅在Spring Web MVC场景(如ContextLoader和FrameworkServlet)中初始化其关联的ConfigurableApplicationContext实例。

applyInitializers方法访问性的设计不是非常理想,它不应该为protected。因为当SpringApplication的子类覆盖该方法时,假设子类实现不调用super的实现,那么基于Spring工厂加载机制的ApplicationContextInitializer集合将不复存在。反之即使子类复用super实现,无论super.applyInitializers(ConfigurableApplicationContext)语句在扩展实现的前或后执行,基于Spring工厂加载机制的ApplicationContextInitializer集合都会独立执行(父类SpringApplication实现),假设子类方法自行扩展ApplicationContextInitializer来源,那么父类与子类的ApplicationContextInitializer集合执行不同调,这样无论给SpringApplication扩展实现的开发人员还是使用该扩展的开发人员均会面临风险。因此建议不要覆盖applyInitializers方法的实现,仅使用ApplicationContextInitializer基于Spring工厂加载机制的扩展方式。

接下来执行Spring应用上下文准备阶段中的最后一个方法,SpringApplicationRunListeners.contextPrepared(ConfigurableApplicationContext)。

1.8.1.4、执行SpringApplicationRunListeners.contextPrepared方法回调

当Spring应用上下文创建并准备完毕时,该方法被回调,不过该方法在ApplicationContext加载配置源之前执行,也是后续讨论的重点。

在讨论SpringApplicationRunListener的内容时,已知默认触发的方法来自实现类EventPublishingRunListener:

	@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));}

至此,Spring应用上下文准备阶段讨论结束,接下来讨论Spring应用上下文进入运行前准备的第二阶段,Spring应用上下文装载阶段。

1.8.2、Spring应用上下文装载阶段

按照SpringApplication.prepareContext方法的实现,本阶段又可划分为四个过程,分别为:注册Spring Boot Bean、设置bean定义lazy init、合并Spring应用上下文配置源、加载Spring应用上下文配置源和回调SpringApplicationRunListener.contextLoaded方法。

1.8.2.1、注册Spring Boot Bean

SpringApplication.prepareContext方法将之前创建的ApplicationArguments对象和可能存在的Banner实例注册为Spring单体Bean:

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {...ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {beanFactory.registerSingleton("springBootBanner", printedBanner);}...}
1.8.2.2、设置bean定义lazy init

在Spring Boot增加一个setLazyInitialization方法,SpringApplication.prepareContext方法根据setLazyInitialization方法设置的boolean参数lazyInitialization来决定是否添加bean LazyInitializationBeanFactoryPostProcessor:

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {...if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}...}public void setLazyInitialization(boolean lazyInitialization) {this.lazyInitialization = lazyInitialization;}

LazyInitializationBeanFactoryPostProcessor用来对未对其bean definition设置lazyInit属性的bean设置lazyInit=true,但前提会使用一组LazyInitializationExcludeFilter对bean进行一层过滤,只有次接口方法返回为false的才可对其进行设置。

public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// Take care not to force the eager init of factory beans when getting filtersCollection<LazyInitializationExcludeFilter> filters = beanFactory.getBeansOfType(LazyInitializationExcludeFilter.class, false, false).values();for (String beanName : beanFactory.getBeanDefinitionNames()) {BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);if (beanDefinition instanceof AbstractBeanDefinition) {postProcess(beanFactory, filters, beanName, (AbstractBeanDefinition) beanDefinition);}}}private void postProcess(ConfigurableListableBeanFactory beanFactory,Collection<LazyInitializationExcludeFilter> filters, String beanName,AbstractBeanDefinition beanDefinition) {Boolean lazyInit = beanDefinition.getLazyInit();if (lazyInit != null) {return;}Class<?> beanType = getBeanType(beanFactory, beanName);if (!isExcluded(filters, beanName, beanDefinition, beanType)) {beanDefinition.setLazyInit(true);}}private Class<?> getBeanType(ConfigurableListableBeanFactory beanFactory, String beanName) {try {return beanFactory.getType(beanName, false);}catch (NoSuchBeanDefinitionException ex) {return null;}}private boolean isExcluded(Collection<LazyInitializationExcludeFilter> filters, String beanName,AbstractBeanDefinition beanDefinition, Class<?> beanType) {if (beanType != null) {for (LazyInitializationExcludeFilter filter : filters) {if (filter.isExcluded(beanName, beanDefinition, beanType)) {return true;}}}return false;}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}}
@FunctionalInterface
public interface LazyInitializationExcludeFilter {boolean isExcluded(String beanName, BeanDefinition beanDefinition, Class<?> beanType);static LazyInitializationExcludeFilter forBeanTypes(Class<?>... types) {return (beanName, beanDefinition, beanType) -> {for (Class<?> type : types) {if (type.isAssignableFrom(beanType)) {return true;}}return false;};}}

LazyInitializationExcludeFilter 接口提供了一个静态方法forBeanTypes返回一个针对指定类型的LazyInitializationExcludeFilter 。

1.8.2.3、合并Spring应用上下文配置源

合并Spring应用上下文配置源的操作由getAllSources()方法实现,该方法是从Spring Boot2.0开始引入的,且较为复杂:

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {...Set<Object> sources = getAllSources();...}public Set<Object> getAllSources() {Set<Object> allSources = new LinkedHashSet<>();if (!CollectionUtils.isEmpty(this.primarySources)) {allSources.addAll(this.primarySources);}if (!CollectionUtils.isEmpty(this.sources)) {allSources.addAll(this.sources);}return Collections.unmodifiableSet(allSources);}public void setSources(Set<String> sources) {Assert.notNull(sources, "Sources must not be null");this.sources = new LinkedHashSet<>(sources);}

不难看出getAllSources()方法返回值为只读Set,它由两个子集组合成:属性primarySources和sources,前者来自SpringApplication构造器参数,后者来源于setSources方法,而该方法的参数为Set<String>类型,用于存储Configuration Class类名、包名及Spring XML配置资源路径。

1.8.2.4、加载Spring应用上下文配置源

load(ApplicationContext, Object[])方法将承担加载Spring应用上下文配置源的职责:

	protected void load(ApplicationContext context, Object[] sources) {if (logger.isDebugEnabled()) {logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));}BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);if (this.beanNameGenerator != null) {loader.setBeanNameGenerator(this.beanNameGenerator);}if (this.resourceLoader != null) {loader.setResourceLoader(this.resourceLoader);}if (this.environment != null) {loader.setEnvironment(this.environment);}loader.load();}

该方法将Spring应用上下文Bean装载的任务交给了BeanDefinitionLoader,且该实现类从Spring Boot1.0就开始引入:

class BeanDefinitionLoader {private final Object[] sources;private final AnnotatedBeanDefinitionReader annotatedReader;private final XmlBeanDefinitionReader xmlReader;private BeanDefinitionReader groovyReader;private final ClassPathBeanDefinitionScanner scanner;private ResourceLoader resourceLoader;...
}

BeanDefinitionLoader 组合了多个属性,第一个属性为SpringApplication.getAllSources()方法返回值,而属性annotatedReader、xmlReader、groovyReader分别为注解驱动实现AnnotatedBeanDefinitionReader、XML配置实现 XmlBeanDefinitionReader 和Groovy实现GroovyBeanDefinitionReader。其中AnnotatedBeanDefinitionReader与ClassPathBeanDefinitionScanner 配合形成AnnotationConfigApplicationContext扫描和注册配置类的基础,随后这些配置类将被解析为Bean定义BeanDefinition:

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {private final AnnotatedBeanDefinitionReader reader;private final ClassPathBeanDefinitionScanner scanner;...public void register(Class<?>... annotatedClasses) {Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");this.reader.register(annotatedClasses);}public void scan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");this.scanner.scan(basePackages);}...
}

而XmlBeanDefinitionReader 和GroovyBeanDefinitionReader则是注解@ImportResource读取BeanDefinition的底层实现。当@ImportResource.locations属性值以".groovy"结尾时,采用GroovyBeanDefinitionReader读取BeanDefinition,否则是XmlBeanDefinitionReader 。不难看出Spring Boot中的BeanDefinitionLoader是以上BeanDefinition读取的综合实现。当其load()方法调用时,这些BeanDefinitionReader类型的属性各司其职,为Spring应用上下文从不同的配置源装载Spring Bean定义。不过执行load()方法后,Spring应用上下文并没有启动:

	int load() {int count = 0;for (Object source : this.sources) {count += load(source);}return count;}private int load(Object source) {Assert.notNull(source, "Source must not be null");if (source instanceof Class<?>) {return load((Class<?>) source);}if (source instanceof Resource) {return load((Resource) source);}if (source instanceof Package) {return load((Package) source);}if (source instanceof CharSequence) {return load((CharSequence) source);}throw new IllegalArgumentException("Invalid source type " + source.getClass());}private int load(CharSequence source) {String resolvedSource = this.xmlReader.getEnvironment().resolvePlaceholders(source.toString());// Attempt as a Classtry {return load(ClassUtils.forName(resolvedSource, null));}catch (IllegalArgumentException | ClassNotFoundException ex) {// swallow exception and continue}// Attempt as resourcesResource[] resources = findResources(resolvedSource);int loadCount = 0;boolean atLeastOneResourceExists = false;for (Resource resource : resources) {if (isLoadCandidate(resource)) {atLeastOneResourceExists = true;loadCount += load(resource);}}if (atLeastOneResourceExists) {return loadCount;}// Attempt as packagePackage packageResource = findPackage(resolvedSource);if (packageResource != null) {return load(packageResource);}throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");}

当Spring应用上下文配置源加载完毕后,紧接着执行SpringApplicationRunListener.contextLoaded方法回调。

1.8.2.5、执行SpringApplicationRunListener.contextLoaded方法回调

EventPublishingRunListener是SpringApplicationRunListener唯一的实现。

	@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {for (ApplicationListener<?> listener : this.application.getListeners()) {if (listener instanceof ApplicationContextAware) {((ApplicationContextAware) listener).setApplicationContext(context);}context.addApplicationListener(listener);}this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));}

由于此时的ApplicationContext尚未启动,故应用可通过监听事件ApplicationPreparedEvent来调整SpringApplication和ApplicationContext对象。
当SpringApplicationRunListener.contextLoaded方法执行后,Spring应用上下文运行前准备的各个操作都执行完毕。接下来Spring应用上下文进入了实质性的启动阶段。

2、Spring应用上下文启动阶段

本阶段的执行由SpringApplication#refreshContext方法实现:

	private void refreshContext(ConfigurableApplicationContext context) {refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();}catch (AccessControlException ex) {// Not allowed in some environments.}}}protected void refresh(ApplicationContext applicationContext) {Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);((AbstractApplicationContext) applicationContext).refresh();}

refreshContext首先调用refresh执行ApplicationContext 的启动。随后默认情况下Spring应用上下文将注册shutdownHook线程,实现优雅的Spring Bean销毁生命周期回调(registerShutdownHook的默认值为true)。

refresh(ApplicationContext)又是一个糟糕的设计,问题在于方法的参数类型上,从Spring Boot1.0开始,SpringApplication.setApplicationContextClass(Class)方法的参数类型必须是ConfigurableApplicationContext 的子类,同时SpringApplication.createApplicationContext()方法的返回类型也是ConfigurableApplicationContext 。换言之refresh(ApplicationContext)的参数类型没有必要选择更抽象的ApplicationContext,并且该方法仅被一处调用。同时该方法定的访问性同样为protected,再次暗示开发人员子类可以覆盖该方法的实现,然后ApplicationContext接口并没有提供refresh()方法,相反该方法出现在其子接口ConfigurableApplicationContext 中。

3、Spring应用上下文启动后阶段

实际上,SpringApplication#(ConfigurableApplicationContext , ApplicationArguments)方法并未给Spring应用上下文启动后阶段提供实现,而是将其交给开发人员自行扩展:

	protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {}

3.1、执行SpringApplicationRunListener.started方法回调

当afterRefresh方法执行完毕时,该方法被回调。

	@Overridepublic void started(ConfigurableApplicationContext context) {context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));}

当Spring Boot事件ApplicationStartedEvent广播结束后,ApplicationRunner和CommandLineRunner Bean随之被执行。

3.2、执行ApplicationRunner和CommandLineRunner

ApplicationRunner和CommandLineRunner均有run方法,在SpringApplication.run()方法完成之前执行,其中CommandLineRunner接口提供简单的字符型数组作为参数,而ApplicationRunner则使用ApplicationArguments。

当Spring应用上下文出现多个ApplicationRunner和CommandLineRunner Bean时,通过实现Ordered接口或标注@Order注解的方式来控制他们的执行顺序。

ApplicationRunner和CommandLineRunner的使用场景

在Spring Boot中,ApplicationStartedEvent事件监听回调略早于ApplicationRunner或CommandLineRunner的run方法执行,然而他们均在SpringApplication和ConfigurableApplicationContext 准备妥当之后,并在SpringApplication.run执行完成之前,因此两者的生命周期回调时并没有本质区别。那么为什么要引入ApplicationRunner和CommandLineRunner?

Spring Boot事件监听器均由Spring工厂加载机制加载并初始化,它们并非Spring Bean,因此无法享受注解驱动和Bean生命周期回调接口的福利。然而这也并不意味着ApplicationStartedEvent事件ApplicationListener实现无法获取依赖的Spring Bean,因为ApplicationStartedEvent同样关联ConfigurableApplicationContext 对象,相反ApplicationRunner和CommandLineRunner能够获得这样的编程便利性,不过两者却无法获取SpringApplication对象,所以各有利弊,从变成结果上看,两者没有差异。

这篇关于SpringApplication运行阶段的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/430423

相关文章

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

如何在运行时修改serialVersionUID

优质博文:IT-BLOG-CN 问题 我正在使用第三方库连接到外部系统,一切运行正常,但突然出现序列化错误 java.io.InvalidClassException: com.essbase.api.base.EssException; local class incompatible: stream classdesc serialVersionUID = 90314637791991

笔记整理—内核!启动!—kernel部分(2)从汇编阶段到start_kernel

kernel起始与ENTRY(stext),和uboot一样,都是从汇编阶段开始的,因为对于kernel而言,还没进行栈的维护,所以无法使用c语言。_HEAD定义了后面代码属于段名为.head .text的段。         内核起始部分代码被解压代码调用,前面关于uboot的文章中有提到过(eg:zImage)。uboot启动是无条件的,只要代码的位置对,上电就工作,kern

win7+ii7+tomcat7运行javaWeb开发的程序

转载请注明出处:陈科肇 1.前提准备: 操作系统:windows 7 旗舰版   x64 JDK:jdk1.7.0_79_x64(安装目录:D:\JAVA\jdk1.7.0_79_x64) tomcat:32-bit64-bit Windows Service Installer(安装目录:D:\0tomcat7SerV) tomcat-connectors:tomcat-connect

php 7之PhpStorm + Nginx + Xdebug运行调试

操作环境: windows PHP 7.1.10 PhpStorm-2017.2.4 Xdebug 2.5.4 Xdebug helper 1.6.1 nginx-1.12.2 注意查看端口占用情况 netstat -ano //查看所以端口netstat -aon|findstr "80" //查看指定端口占用情况 比如80端口查询情况 TCP 0.0.0.0:8

[轻笔记] ubuntu Shell脚本实现监视指定进程的运行状态,并能在程序崩溃后重启动该程序

根据网上博客实现,发现只能监测进程离线,然后对其进行重启;然而,脚本无法打印程序正常状态的信息。自己通过不断修改测试,发现问题主要在重启程序的命令上(需要让重启的程序在后台运行,不然会影响监视脚本进程,使其无法正常工作)。具体程序如下: #!/bin/bashwhile [ 1 ] ; dosleep 3if [ $(ps -ef|grep exe_name|grep -v grep|

stl的sort和手写快排的运行效率哪个比较高?

STL的sort必然要比你自己写的快排要快,因为你自己手写一个这么复杂的sort,那就太闲了。STL的sort是尽量让复杂度维持在O(N log N)的,因此就有了各种的Hybrid sort algorithm。 题主你提到的先quicksort到一定深度之后就转为heapsort,这种是introsort。 每种STL实现使用的算法各有不同,GNU Standard C++ Lib

8阶段项目:五子棋(附带源码)

8阶段项目:五子棋 8.1-技术实现 1.静态变量 静态变量只能定义在类中,不能定义在方法中。静态变量可以在static修饰的方法中使用,也可以在非静态的方法中访问。主要解决在静态方法中不能访问非静态的变量。 2.静态方法 静态方法就相当于一个箱子,只是这个箱子中装的是代码,需要使用这些代码的时候,就把这个箱子放在指定的位置即可。   /*** 静态变量和静态方法*/public cl

Docker进入容器并运行命令

在讨论如何使用Docker进入容器并运行命令时,我们需要先理解Docker的基本概念以及容器的工作原理。Docker是一个开放平台,用于开发、交付和运行应用程序。它使用容器来打包、分发和运行应用程序,这些容器是轻量级的、可移植的、自包含的,能够在几乎任何地方以相同的方式运行。 进入Docker容器的几种方式 1. 使用docker exec命令 docker exec命令是最常用的进入正在运