SpringApplication初始化阶段

2023-11-28 22:32

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

在Spring Framework时代,Spring应用上下文通常由容器启动,如ContextLoaderListener或WebApplicationInitializer的实现类由Servlet容器装载并驱动。到了Spring Boot时代,Spring应用上下文的启动则通过调用SpringApplication.run(Object,String …)或SpringApplicationBuilder.run(String …)方法并配合@SpringBootApplication或@EnableAutoConfiguration注解方式完成。

从Spring Boot应用进程来看,整体的生命周期大体上总结如下:

  • SpringApplication初始化阶段
  • SpringApplication运行阶段
  • SpringApplication结束阶段
  • SpringApplication应用退出

SpringApplication初始化阶段属于运行前的准备阶段,大多数Spring Boot应用直接或间接地使用SpringApplication API驱动Spring应用,SpringApplication 允许指定应用类型,Web还是非Web,Banner的输出、配置默认属性的内容等,这些状态变更的操作只要在run()方法之前指定即可。简单而言,SpringApplication 的准备阶段主要由两阶段完成:构造阶段和配置阶段。

1、SpringApplication构造阶段

SpringApplication构造阶段在其构造器中完成,在日常开发中,很少直接与SpringApplication构造器打交道,而是调用其静态方法run(Class,String …)来启动应用,实际上其构造过程就在其中:

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

无论使用哪个方法都需要Class类型的primarySource 参数,这个参数有什么意义呢?

1.1、理解SpringApplication主配置类

SpringApplication主配置类的概念和实现是从Spring Boot2.0开始引入的。通常情况下,引导类将作为primarySource参数的内容,这些引导类基本上具备一个特点,不是标注@SpringBootApplication就是标注@EnableAutoConfiguration,由于@SpringBootApplication元标注@EnableAutoConfiguration,因此两者均被底层Spring应用上下文视作@EnableAutoConfiguration处理。当引导类作为primarySource参数时,Spring应用上下文将其视为Configuration Class处理。

主配置类属性primarySource除初始化构造器参数外,还能通过SpringApplication.addPrimarySources()方法追加修改:

	private Set<Class<?>> primarySources;public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {this.primarySources.addAll(additionalPrimarySources);}

1.2、SpringApplication的构造过程

已知SpringApplication.run()方法的执行会伴随SpringApplication对象的构造,其调用的构造器为:

	public SpringApplication(Class<?>... primarySources) {this(null, primarySources);}@SuppressWarnings({ "unchecked", "rawtypes" })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();setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = deduceMainApplicationClass();}

根据调用链路,实际执行的构造器为SpringApplication(ResourceLoader, Class<?>),其中主配置类primarySources被SpringApplication对象primarySources属性存储,随后依次调用deduceFromClasspath()、setInitializers()、setListeners()和deduceMainApplicationClass()方法。按照方法名的语义,他们分别为:推断Web应用类型、加载Spring应用上下文初始化器、加载Spring应用事件监听器和推断应用引导类。

1.3、推断Web应用类型

推断Web应用类型属于当前Spring Boot应用Web类型的初始化过程,因为该类型可在SpringApplication构造后及run方法之前,再通setWebApplicationType(WebApplicationType)方法调整。在推断Web应用类型的过程中,由于当前Spring应用上下文尚未准备,所以实现采用的是检查当前ClassLoader下基准Class的存在性判断:

public enum WebApplicationType {NONE,SERVLET,REACTIVE;private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";static WebApplicationType deduceFromClasspath() {if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}return WebApplicationType.SERVLET;}
}

WebApplicationType.deduceFromClasspath()利用ClassUtils.isPresent(String,ClassLoader)方法依次判断DispatcherHandler、DispatcherServlet、ServletContainer、Servlet、ConfigurableWebApplicationContext的存在性组合情况,从而推断Web应用类型:

  • 当DispatcherHandler存在,并且DispatcherServlet和ServletContainer不存在时,换言之,Spring Boot仅依赖WebFlux存在时,此时的Web应用类型为REACTIVE。
  • 当Servlet和ConfigurableWebApplicationContext均不存在时,当前应用为非Web应用,即NONE。
  • 其余情况为SERVLET。

当WebApplicationType.deduceFromClasspath()执行完毕,SpringApplication的构造进入加载Spring应用上下文初始化器的过程。

1.4、加载Spring应用上下文初始化器(ApplicationContextInitializer)

	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;}

此处同样运用了Spring工厂加载机制方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)。结合本例的场景,该方法将返回所有META-INF/spring.factory资源中配置的ApplicationContextInitializer实现类名单:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

当getSpringFactoriesInstances方法获取实现类名单后,调用createSpringFactoriesInstances方法初始化这些实现类:

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;}

传递空数组的parameterTyps和args方法参数,则ApplicationContextInitializer实现类必须存在默认构造器。挑选ContextIdApplicationContextInitializer进行检验:

public class ContextIdApplicationContextInitializerimplements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {private int order = Ordered.LOWEST_PRECEDENCE - 10;public void setOrder(int order) {this.order = order;}@Overridepublic int getOrder() {return this.order;}@Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {ContextId contextId = getContextId(applicationContext);applicationContext.setId(contextId.getId());applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);}private ContextId getContextId(ConfigurableApplicationContext applicationContext) {ApplicationContext parent = applicationContext.getParent();if (parent != null && parent.containsBean(ContextId.class.getName())) {return parent.getBean(ContextId.class).createChildId();}return new ContextId(getApplicationId(applicationContext.getEnvironment()));}private String getApplicationId(ConfigurableEnvironment environment) {String name = environment.getProperty("spring.application.name");return StringUtils.hasText(name) ? name : "application";}static class ContextId {private final AtomicLong children = new AtomicLong(0);private final String id;ContextId(String id) {this.id = id;}ContextId createChildId() {return new ContextId(this.id + "-" + this.children.incrementAndGet());}String getId() {return this.id;}}
}

当SpringApplication构建器执行该方法后,加载Spring应用事件监听器的动作立即执行。

1.5、加载Spring应用事件监听器(ApplicationContextInitializer)

与加载ApplicationContextInitializer逻辑相同。

1.6、推断应用引导类

推断应用引导类是SpringApplication构造过程的末尾动作,其执行方法为deduceMainApplicationClass():

	private Class<?> deduceMainApplicationClass() {try {StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}}catch (ClassNotFoundException ex) {// Swallow and continue}return null;}

该方法根据当前线程执行栈来判断其栈中哪个类包含main方法。尽管这个方法的实现并不严谨,不过可覆盖绝大多数以Java的标准main方法引导的情况。
至此在SpringApplication构造的过程中,SpringApplication属性primarySources、webApplicationType、initializers、listeners和mainApplicationClass均得到了初始化,下面讨论他们在不同阶段所扮演的角色。

2、SpringApplication配置阶段

配置阶段位于构造阶段和运行阶段之间,该阶段是可选的,主要用于调整或补充构造阶段的状态、左右运行时行为,以SpringApplication setter方法为代表,用于调整SpringApplication的行为,补充行为则以add*方法为主,或许通过setter方法配置SpringApplication 过于繁琐,因此Spring Boot引入SpringApplicationBuilder以提升API的便利性,多数情况下无须调整SpringApplication的默认状态。

2.1、自定义SpringApplication

自定义SpringApplication一共有三种方式:

  • 调整SpringApplication设置
  • 增加SpringApplication配置源
  • 调整Spring Boot外部化配置

2.2、调整SpringApplication设置

几乎所有SpringApplication调整应用行为的方法都与SpringApplicationBuilder方法一一对应:

  • SpringApplication方法API
  • SpringApplicationBuilder方法API

2.3、增加SpringApplication配置源

SpringApplication构造参数primarySources是从Spring Boot2.0开始引入的,作为主配置类,还有一个sources属性充当配置源的角色,分别来自@Configuration Class、XML配置文件和package。

	public void setSources(Set<String> sources) {Assert.notNull(sources, "Sources must not be null");this.sources = new LinkedHashSet<>(sources);}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);}

2.4、调整Spring Boot外部化配置

Spring Boot允许将配置外部化,以便在不同的环境中使用相同的应用程序代码。可以使用各种外部配置源,包括Java属性文件、YAML文件、环境变量和命令行参数。

属性值可以通过@Value注释直接注入到bean中,通过Spring的环境抽象访问,或者通过@ConfigurationProperties绑定到结构化对象。

Spring Boot使用了一个非常特殊的PropertySource排序,它被设计成允许合理地重写值。属性按以下顺序考虑(来自较低项的值将覆盖较早项的值):

  1. 默认属性(通过设置SpringApplication.setDefaultProperties指定)。
  2. @Configuration类上的@PropertySource注解。请注意,在刷新应用程序上下文之前,不会将此类属性源添加到环境中。现在配置某些属性(如logging.和spring.main.)为时已晚,这些属性将在刷新开始前读取。
  3. 配置数据(如application.properties文件)
  4. 一个RandomValuePropertySource,它只在random.*中具有属性。
  5. 操作系统环境变量。
  6. Java系统属性(System.getProperties())。
  7. 来自的JNDI属性java:comp/env.
  8. ServletContext初始化参数。
  9. ServletConfig初始化参数。
  10. SPRING_APPLICATION_JSON属性(嵌入在环境变量或系统属性中的内联JSON)。
  11. 命令行参数。
  12. 属性属性。可在@SpringBootTest和测试注解上获得,用于测试应用程序的特定部分。
  13. 测试类上的@TestPropertySource注解。
  14. Devtools处于活动状态时,$HOME/.config/spring 引导目录中的Devtools全局设置属性。

配置数据文件按以下顺序考虑:

  1. 打包在jar中的应用程序属性(application.properties和YAML变体)。
  2. 打包在jar中的特定profile配置文件的应用程序属性(application-{profile}.properties和YAML变体)。
  3. 打包jar之外的应用程序属性(application.properties和YAML变体)。
  4. 在打包的jar之外配置特定于应用程序的属性(application-{profile}.properties和YAML变体)。

建议对整个应用程序使用一种格式。如果在同一位置有同时具有.properties和.yml格式的配置文件,则优先使用.properties。

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



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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

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

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

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

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

@postconstruct初始化的操作

从Java EE 5规范开始,Servlet中增加了两个影响Servlet生命周期的注解(Annotion);@PostConstruct和@PreDestroy。这两个注解被用来修饰一个非静态的void()方法 。写法有如下两种方式: @PostConstruct Public void someMethod() {}

spring和tomcat初始化的类和注解

1.InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。 spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中同过init-method指定,两种方式可以同时使用 实

LabVIEW环境中等待FPGA模块初始化完成

这个程序使用的是LabVIEW环境中的FPGA模块和I/O模块初始化功能,主要实现等待FAM(Field-Programmable Gate Array Module,FPGA模块)的初始化完成,并处理初始化过程中的错误。让我们逐步分析各部分的功能: 1. Wait for FAM Initialization框架 此程序框架用于等待I/O模块成功初始化。如果在5秒钟内模块没有完成配

dp(背包问题) 恰好、至少、至多初始化

状态表示的初始化(一般情况) f[i][j] i:前i件物品 体积至少为j 枚举体积时可以是负数(体积为负数时等价于体积为0) max f[i][j] = {-0x3f} f[i][0] = 0min f[i][j] = { 0x3f} f[i][0] = 0cnt f[0][0] = 1 体积至多为j 枚举体积时不能是负数 max f[i][j] = 0min f[i][j]

2023 CCPC(秦皇岛)现场(第二届环球杯.第 2 阶段:秦皇岛)部分题解

所有题目链接:Dashboard - The 2023 CCPC (Qinhuangdao) Onsite (The 2nd Universal Cup. Stage 9: Qinhuangdao) - Codeforces 中文题面: contest-37054-zh.pdf (codeforces.com) G. Path 链接: Problem - G - Codeforces

【笔记-流程记录】从零开始做一个人形怪兽(建模阶段)

大型 1.第一步还是找素模,打开材质球,吸管点一下,就会出现素模的贴图,一共有四个 比如,点进去第一个,再点漫反射,再点psd就会得到相应的贴图 2.然后我们依然是面片然后插入参考图 如果透视窗口啥都没有,按g也不显示线框。那按下z(居中视图),然后再试一下按G显示栅格。 3.导入素模,重置变换 注释:重置变换是一个非常有用的功能,主要用于将对象的变换属性(位置、旋