大家好,又见面了,我是你们的朋友全栈君。
SpringApplication运行阶段围绕run(String …)方法展开,该过程结合初始化阶段完成的状态进一步完善了运行时所需要准备的资源,随后启动Spring应用上下文,在此期间伴随Spring Boot和Spring事件的触发,形成完整的SpringApplication生命周期:
- SpringApplication准备阶段
- SpringApplication启动阶段
- SpringApplication启动后阶段
1、SpringApplication准备阶段
本阶段涉及的范围从run(String …)方法调用开始,到refreshContext(ConfigurableApplicationContext)调用前:
代码语言:javascript复制 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)方法创建的:
代码语言:javascript复制 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的集合:
代码语言:javascript复制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方法:
代码语言:javascript复制 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 duplicates
Set<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方法:
代码语言:javascript复制 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内建实现如下:
代码语言:javascript复制# Run Listeners
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener
EventPublishingRunListener作为Spring Boot唯一的内建实现,完全符合上述构造器参数签名的约束:
代码语言:javascript复制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);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public 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() | ApplicationStartingEvent | 1.0 |
environmentPrepared(ConfigurableEnvironment) | ApplicationEnvironmentPreparedEvent | 1.0 |
contextPrepared(ConfigurableApplicationContext) | ApplicationContextInitializedEvent | 1.0 |
contextLoaded(ConfigurableApplicationContext) | ApplicationPreparedEvent | 1.0 |
started(ConfigurableApplicationContext context) | ApplicationStartedEvent | 2.0 |
running(ConfigurableApplicationContext) | ApplicationReadyEvent | 2.0 |
failed(ConfigurableApplicationContext, Throwable) | ApplicationFailedEvent | 2.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应用程序运行时,应用程序事件按以下顺序发送:
- ApplicationStartingEvent在运行开始时但在任何处理之前发送,侦听器和初始化器的注册除外。
- 当上下文中要使用的环境已知但在创建上下文之前,将发送ApplicationEnvironmentPreparedEvent。
- 在准备ApplicationContext并且调用ApplicationContextInitializers时,但在加载任何bean定义之前,发送ApplicationContextInitializedEvent。
- ApplicationPreparedEvent在刷新开始之前发送,但在加载bean定义之后发送。
- ApplicationStartedEvent在刷新上下文之后、调用任何应用程序和命令行运行程序之前发送。
- AvailabilityChangeEvent紧接着以LivenessState.CORRECT发送,以指示应用程序被视为活动的。
- 在调用任何应用程序和命令行运行程序之后,将发送ApplicationReadyEvent。
- AvailabilityChangeEvent在with ReadinessState.ACCEPTINGu通信之后立即发送,以指示应用程序已准备好为请求提供服务。
- 如果启动时出现异常,则会发送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对象中:
代码语言:javascript复制 @Override
public 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
代码语言:javascript复制# 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
代码语言:javascript复制# Application Listeners
org.springframework.context.ApplicationListener=
org.springframework.boot.autoconfigure.BackgroundPreinitializer
ApplicationListener实现 | 监听事件 | 场景说明 | 引入版本 |
---|---|---|---|
ClearCachesApplicationListener | ContextRefreshedEvent | 清除ReflectionUtils和ClassLoader缓存 | 1.4 |
ParentContextCloserApplicationListener | ParentContextAvailableEvent | 如果父上下文对象关闭,则关闭应用程序上下文的侦听器。它侦听刷新事件并从中获取当前上下文,然后侦听关闭的事件并将其传播到层次结构中 | 1.0 |
CloudFoundryVcapEnvironmentPostProcessor | ApplicationPreparedEvent | 从延迟日志记录切换到立即日志记录到指定的目标 | 1.3 |
FileEncodingApplicationListener | ApplicationEnvironmentPreparedEvent | 检测spring.mandatory-file-encoding属性是否与系统属性file.encoding匹配 | 1.0 |
AnsiOutputApplicationListener | ApplicationEnvironmentPreparedEvent | 生成ANSI编码的输出,自动尝试检测终端是否支持ANSI | 1.2 |
ConfigFileApplicationListener | ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent | 通过从已知文件位置加载属性来配置上下文环境。默认情况下,属性将从“application.properties”和/或“application.yml”文件加载 | 1.0 |
DelegatingApplicationListener | ApplicationEnvironmentPreparedEvent | 将Spring事件委派给配置context.listener.classes所指定的多个ApplicationListener实现类 | 1.0 |
ClasspathLoggingApplicationListener | ApplicationEnvironmentPreparedEvent、ApplicationFailedEvent | DEBUG级别日志记录当前用的Class Path | 1.0 |
LoggingApplicationListener | ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent、ContextClosedEvent、ApplicationFailedEvent | 识别日志框架并加载日志配置文件 | 1.0 |
LiquibaseServiceLocatorApplicationListener | ApplicationStartingEvent | 取代qiquibase ServiceLocator | 1.0 |
BackgroundPreinitializer | ApplicationStartingEvent、ApplicationReadyEvent、ApplicationFailedEvent | ApplicationListener在后台线程中触发耗时任务的早期初始化。 | 1.3 |
以上最重要的Spring Boot内建事件监听器莫过于ConfigFileApplicationListener 和LoggingApplicationListener ,前者负责Spring Boot应用配置属性文件的加载,后者用于Spring Boot日志系统的初始化(日志框架识别、日志配置文件加载等)。
1.5、装配ApplicationArguments
当执行SpringApplicationRunListener.starting()方法后,SpringApplication运行进入装配ApplicationArguments逻辑:
代码语言:javascript复制 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:
代码语言:javascript复制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回调方法参数的运用:
代码语言:javascript复制@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
当ApplicationArguments 实例准备完毕后,SpringApplication的执行操作进入准备ConfigurableEnvironment的阶段。
1.6、准备ConfigurableEnvironment
代码语言:javascript复制 private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment 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:
代码语言:javascript复制 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。
代码语言:javascript复制 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应用组件的核心引擎:
代码语言:javascript复制 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截止:
代码语言:javascript复制 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:
代码语言:javascript复制public class StandardReactiveWebEnvironment extends StandardEnvironment implements ConfigurableReactiveWebEnvironment {
}
所以StandardReactiveWebEnvironment 等于StandardEnvironment ,所以SpringApplication.getOrCreateEnvironment()方法返回值适用于以上三种不同的应用类型。既然AbstractApplicationContext.createEnvironment()与SpringApplication.getOrCreateEnvironment()的返回结果基本相同,为何SpringApplication要提前设置呢?其根本原因在于AbstractApplicationContext.createEnvironment()方法的执行时机。该方法的执行调用链路为refresh()->prepareRefresh()->getEnvironment()->createEnvironment():
代码语言:javascript复制 public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
...
}
...
}
protected void prepareRefresh() {
...
getEnvironment().validateRequiredProperties();
...
}
@Override
public 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)方法的命名而来的:
代码语言:javascript复制 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实例:
代码语言:javascript复制 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:
代码语言:javascript复制 @Override
public 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:
代码语言:javascript复制 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:
代码语言:javascript复制 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的才可对其进行设置。
代码语言:javascript复制public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// Take care not to force the eager init of factory beans when getting filters
Collection<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;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
代码语言:javascript复制@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开始引入的,且较为复杂:
代码语言:javascript复制 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应用上下文配置源的职责:
代码语言:javascript复制 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就开始引入:
代码语言:javascript复制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:
代码语言:javascript复制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应用上下文并没有启动:
代码语言:javascript复制 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 Class
try {
return load(ClassUtils.forName(resolvedSource, null));
}
catch (IllegalArgumentException | ClassNotFoundException ex) {
// swallow exception and continue
}
// Attempt as resources
Resource[] 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 package
Package 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唯一的实现。
代码语言:javascript复制 @Override
public 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方法实现:
代码语言:javascript复制 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应用上下文启动后阶段提供实现,而是将其交给开发人员自行扩展:
代码语言:javascript复制 protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
3.1、执行SpringApplicationRunListener.started方法回调
当afterRefresh方法执行完毕时,该方法被回调。
代码语言:javascript复制 @Override
public 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对象,所以各有利弊,从变成结果上看,两者没有差异。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/195791.html原文链接:https://javaforall.cn