范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

详细分析SpringBoot启动流程

  组里的实习生妹妹讲了一次Springboot的启动,
  讲着讲着就讲到Spring的bean的生命周期去了,
  我心想坏了,
  这妮子估计把Springboot和Spring的相关逻辑给混淆了,
  这必须得给她治一治。前言
  本文会对Springboot启动流程进行详细分析。但是请注意,Springboot启动流程是Springboot的逻辑,请千万不要将Springboot启动流程相关逻辑与Spring的相关逻辑混在一起,比如把Spring的bean生命周期的逻辑混在Springboot启动流程中,那么整个体系就复杂且混乱了。
  所以本文仅重点关注Springboot启动流程,涉及Spring的部分,会略作说明并跳过。
  整体的一个结构图如下。
  Springboot版本:2.4.1正文一. Springboot启动流程图及说明
  如下是Springboot的一个启动流程图。
  在SpringApplication完成初始化后,就会调用SpringApplication对象的run() 方法,该方法就是Springboot启动的入口,也对应着全流程图中的开始。下面给出SpringApplication对象的run() 方法说明,如下所示。public ConfigurableApplicationContext run(String... args) {     // 创建StopWatch,用于统计Springboot启动的耗时     StopWatch stopWatch = new StopWatch();     // 开始计时     stopWatch.start();     DefaultBootstrapContext bootstrapContext = createBootstrapContext();     ConfigurableApplicationContext context = null;     configureHeadlessProperty();     // 获取运行时监听器     SpringApplicationRunListeners listeners = getRunListeners(args);     // 调用运行时监听器的starting()方法     // 该方法需要在Springboot一启动时就调用,用于特别早期的初始化     listeners.starting(bootstrapContext, this.mainApplicationClass);     try {         // 获取args参数对象         ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);         // 读取Springboot配置文件并创建Environment对象         // 这里创建的Environment对象实际为ConfigurableEnvironment         ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);         configureIgnoreBeanInfo(environment);         // 打印Banner图标         Banner printedBanner = printBanner(environment);         // 创建ApplicationContext应用行下文,即创建容器         context = createApplicationContext();         context.setApplicationStartup(this.applicationStartup);         // 准备容器         prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);         // 初始化容器         refreshContext(context);         afterRefresh(context, applicationArguments);         // 停止计时         stopWatch.stop();         if (this.logStartupInfo) {             // 打印启动耗时等信息             new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);         }         // 调用运行时监听器的started()方法         // 该方法需要在应用程序启动后,CommandLineRunners和ApplicationRunners被调用前执行         listeners.started(context);         callRunners(context, applicationArguments);     }     catch (Throwable ex) {         handleRunFailure(context, ex, listeners);         throw new IllegalStateException(ex);     }      try {         // 调用运行时监听器的running()方法         // 该方法需要在SpringApplication的run()方法执行完之前被调用         listeners.running(context);     }     catch (Throwable ex) {         handleRunFailure(context, ex, null);         throw new IllegalStateException(ex);     }     return context; }二. SpringApplication的初始化
  通常,Springboot应用程序的启动类定义如下。@SpringBootApplication public class LearnStartApplication {      public static void main(String[] args) {         SpringApplication.run(LearnStartApplication.class, args);     }  }
  从SpringApplication的静态run() 方法一路跟进,会发现如下的实现。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); }
  也就是Springboot启动时会先创建SpringApplication,然后再通过SpringApplication的run() 方法完成启动。所以下面分析一下SpringApplication的初始化逻辑,其构造方法如下所示。public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {     this.resourceLoader = resourceLoader;     Assert.notNull(primarySources, "PrimarySources must not be null");     // 设置源     // 通常Springboot的启动类就是源     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));     // 推断并设置WEB应用程序类型     // 根据classpath下的类来推断     this.webApplicationType = WebApplicationType.deduceFromClasspath();     // 加载并设置Bootstrapper     this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));     // 加载并设置初始化器     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));     // 加载并设置应用事件监听器     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));     // 推断并设置应用程序主类的Class对象     this.mainApplicationClass = deduceMainApplicationClass(); }
  梳理一下在SpringApplication的构造方法中,做了如下事情。设置源。通常,Springboot中的源就是Springboot的启动类;设置WEB应用程序类型。通过判断classpath下是否存在某些类,来推断当前WEB应用程序的类型;加载并设置Bootstrapper,ApplicationContextInitializer和ApplicationListener。借助SpringFactoriesLoader基于SPI机制完成Bootstrapper,ApplicationContextInitializer和ApplicationListener的加载,然后设置到SpringApplication中;设置应用程序主类的Class对象。
  下面对上述事情进行分析。1. 设置源
  这里的源,也就是Spring容器启动时依赖的初始配置类,在Springboot中,初始配置类通常为启动类。下面可以通过调试看一下primarySources字段的值,如下所示。
  可见源就是Springboot的启动类的Class对象。2. 设置WEB应用程序类型
  WebApplicationType#deduceFromClasspath方法如下所示。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"; private static final String SERVLET_APPLICATION_CONTEXT_CLASS                      = "org.springframework.web.context.WebApplicationContext"; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS                      = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";  static WebApplicationType deduceFromClasspath() {     if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)             && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {         // classpath下存在DispatcherHandler,但不存在DispatcherServlet和ServletContainer         // 则WEN应用程序类型推断为REACTIVE,即响应式WEB应用程序         return WebApplicationType.REACTIVE;     }     for (String className : SERVLET_INDICATOR_CLASSES) {         if (!ClassUtils.isPresent(className, null)) {             // 非WEB应用程序             return WebApplicationType.NONE;         }     }     // 基于Servlet的WEB应用程序     return WebApplicationType.SERVLET; }
  在WebApplicationType中预定义了若干种用于判断的类的全限定名,然后在deduceFromClasspath() 方法中使用ClassUtils来判断预定义的类是否存在,通过这样的方式最终可以推断出当前WEB应用程序类型。在示例工程中,如果只引入spring-boot-starter包,那么推断出来的WebApplicationType为NONE,如下所示。
  如果再引入spring-boot-starter-web包,则推断出来的WebApplicationType为SERVLET,如下所示。
  3. 加载并设置Bootstrapper,ApplicationContextInitializer和ApplicationListener
  这里主要分析一下是如何加载Bootstrapper,ApplicationContextInitializer和ApplicationListener的。它们的加载均使用了getSpringFactoriesInstances() 方法,下面看一下实现。private  Collection getSpringFactoriesInstances(Class type) {     return getSpringFactoriesInstances(type, new Class<?>[] {}); }  private  Collection getSpringFactoriesInstances(Class type, Class<?>[] parameterTypes, Object... args) {     ClassLoader classLoader = getClassLoader();     // 通过SpringFactoriesLoader扫描classpath所有jar包的META-INF目录下的spring.factories文件     // 将type全限定名对应的全限定名的集合获取到     Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));     // 实例化     List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);     AnnotationAwareOrderComparator.sort(instances);     return instances; }
  主要就是基于SpringFactoriesLoader完成加载,加载机制和Springboot中的自动装配是一样,唯一的区别就是自动装配中在spring.factories文件中是根据@EnableAutoConfiguration的全限定名作为key去获取全限定名集合,而在这里是根据Bootstrapper,ApplicationContextInitializer和ApplicationListener的全限定名作为key去获取全限定名集合,以spring-boot-autoconfigure包中的spring.factories文件为例,说明如下。org.springframework.context.ApplicationContextInitializer= org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer, org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener  org.springframework.context.ApplicationListener= org.springframework.boot.autoconfigure.BackgroundPreinitializer4. 设置应用程序主类的Class对象
  获取应用程序主类的Class对象的SpringApplication#deduceMainApplicationClass方法如下所示。private Class<?> deduceMainApplicationClass() {     try {         // 通过RuntimeException获取堆栈         StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();         for (StackTraceElement stackTraceElement : stackTrace) {             // 判断堆栈元素的发生方法名是否为main             if ("main".equals(stackTraceElement.getMethodName())) {                 // 通过反射获取到main方法所在类的Class对象                 return Class.forName(stackTraceElement.getClassName());             }         }     }     catch (ClassNotFoundException ex) {              }     return null; }
  获取应用程序主类的Class对象是通过堆栈实现的,下面给出调试截图。
  三. Springboot事件机制
  在Springboot启动的一开始,有一步逻辑是获取运行时监听器,最终会获取到一个SpringApplicationRunListeners对象,下面看一下获取运行时监听器的getRunListeners() 方法的实现。private SpringApplicationRunListeners getRunListeners(String[] args) {     Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };     // 先基于SpringFactoriesLoader的SPI机制获取SpringApplicationRunListener的实现类集合     // 然后创建SpringApplicationRunListeners对象     return new SpringApplicationRunListeners(logger,             getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),             this.applicationStartup); }  SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,     ApplicationStartup applicationStartup) {     // SpringApplicationRunListeners的构造方法中只是进行简单赋值     this.log = log;     this.listeners = new ArrayList<>(listeners);     this.applicationStartup = applicationStartup; }
  在getRunListeners() 方法中会先基于SpringFactoriesLoader的SPI机制将SpringApplicationRunListener接口的实现类获取出来,在spring-boot包中提供了一个SpringApplicationRunListener接口的实现类,为EventPublishingRunListener,这也是Springboot提供的唯一一个内置运行时监听器,所以通过getRunListeners() 方法获取到的SpringApplicationRunListeners对象中持有一个SpringApplicationRunListener的集合,这个集合中默认情况下一定会包含一个EventPublishingRunListener的对象。
  下面再以SpringApplicationRunListeners的starting() 方法为例,分析一下SpringApplicationRunListeners是如何工作的。void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {     doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),             (step) -> {                 if (mainApplicationClass != null) {                     step.tag("mainApplicationClass", mainApplicationClass.getName());                 }             }); }  private void doWithListeners(String stepName, Consumer listenerAction,         Consumer stepAction) {     StartupStep step = this.applicationStartup.start(stepName);     // 集合中每个运行时监听器都会执行listenerAction函数     this.listeners.forEach(listenerAction);     if (stepAction != null) {         stepAction.accept(step);     }         step.end(); }
  结合SpringApplicationRunListeners的starting() 和doWithListeners() 方法,可知SpringApplicationRunListeners会将starting() 方法的调用传递给其持有的每个运行时监听器,所以SpringApplicationRunListeners是组合模式的一个应用。
  那么Springboot中的事件机制按理应该由Springboot提供的唯一一个运行时监听器EventPublishingRunListener实现。下面分析EventPublishingRunListener的逻辑,还是以EventPublishingRunListener的starting() 方法为例,进行说明。public void starting(ConfigurableBootstrapContext bootstrapContext) {     // 先创建一个ApplicationStartingEvent事件对象     // 然后调用SimpleApplicationEventMulticaster来发布事件对象     this.initialMulticaster             .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args)); }
  EventPublishingRunListener的starting() 方法中会先创建ApplicationStartingEvent事件对象,然后通过EventPublishingRunListener持有的一个SimpleApplicationEventMulticaster对象来发布事件。
  那么下面继续分析SimpleApplicationEventMulticaster怎么发布事件,发布给谁,SimpleApplicationEventMulticaster的multicastEvent() 方法如下所示。public void multicastEvent(ApplicationEvent event) {     multicastEvent(event, resolveDefaultEventType(event)); }  public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {     ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));     Executor executor = getTaskExecutor();     // 调用getApplicationListeners()方法将所有适合接收当前事件的ApplicationListener获取出来     // 然后基于异步或者同步的方式向符合条件的ApplicationListener发布事件     for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {         if (executor != null) {             // 异步发布             executor.execute(() -> invokeListener(listener, event));         }         else {             // 同步发布             invokeListener(listener, event);         }     } }
  SimpleApplicationEventMulticaster的multicastEvent() 方法中会先将初始化SpringApplication时加载的ApplicationListener获取到,然后遍历其中适合接收当前事件的ApplicationListener,然后异步或者同步的向ApplicationListener发布事件,继续看invokeListener() 方法,如下所示。protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {     ErrorHandler errorHandler = getErrorHandler();     // 实际调用doInvokeListener()方法来向ApplicationListener发布事件     if (errorHandler != null) {         try {             doInvokeListener(listener, event);         }         catch (Throwable err) {             errorHandler.handleError(err);         }     }     else {         doInvokeListener(listener, event);     } }  private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {     try {         // ApplicationListener接口的实现类都会实现onApplicationEvent()方法         // 在onApplicationEvent()方法中会处理当前接收到的事件         listener.onApplicationEvent(event);     }     catch (ClassCastException ex) {         String msg = ex.getMessage();         if (msg == null || matchesClassCastMessage(msg, event.getClass())) {             Log logger = LogFactory.getLog(getClass());             if (logger.isTraceEnabled()) {                 logger.trace("Non-matching event type for listener: " + listener, ex);             }         }         else {             throw ex;         }     } }
  SimpleApplicationEventMulticaster的invokeListener() 方法中实际会调用到doInvokeListener() 方法,在doInvokeListener() 方法中会调用ApplicationListener的onApplicationEvent() 方法,所以在这里就调用到了ApplicationListener实际处理事件的逻辑。
  现在对Springboot中的事件监听机制进行小结。SpringApplication初始化时会加载所有的ApplicationListener;在Springboot启动的一开始,会调用到SpringApplication#getRunListeners方法创建一个SpringApplicationRunListeners对象;SpringApplicationRunListeners是组合模式的应用,其持有一个SpringApplicationRunListener的集合,集合中默认会存在一个Springboot提供的SpringApplicationRunListener的实现类EventPublishingRunListener,所有对SpringApplicationRunListeners的调用请求都会被传递给集合中的每一个SpringApplicationRunListener;EventPublishingRunListener中持有一个事件发布器SimpleApplicationEventMulticaster,在EventPublishingRunListener的构造函数中,会将SimpleApplicationEventMulticaster创建出来并将SpringApplication中的所有ApplicationListener设置给SimpleApplicationEventMulticaster。当EventPublishingRunListener的starting(),environmentPrepared() 等方法被调用时,EventPublishingRunListener会创建对应的事件(ApplicationStartingEvent,ApplicationEnvironmentPreparedEvent)并通过SimpleApplicationEventMulticaster向适合接收当前事件的ApplicationListener发布;SimpleApplicationEventMulticaster发布事件时,会先获取出所有适合接收当前事件的ApplicationListener,然后调用这些ApplicationListener的onApplicationEvent() 方法,每一个ApplicationListener会在其实现的onApplicationEvent() 方法中完成对事件的处理。
  图示如下。
  四. 外部化配置加载
  Springboot启动时,会在调用运行时监听器的starting() 方法后创建DefaultApplicationArguments对象,然后就会开始加载外部化配置。
  外部化配置通常由application.yml文件(或者application.properties文件)提供,在application.yml文件中添加配置项也是最常用的外部化配置方式。实际上为Springboot应用程序添加外部化配置的方式还有许多种,可以参考Springboot-外部化配置,下面是较为常用的外部化配置方式的优先级(由上到下优先级逐渐降低)。命令行参数,即Command line arguments;JAVA系统属性,即Java System properties(System#getProperties);操作系统环境变量,即OS environment variables;配置数据文件(例如application.yml文件),即Config data(such as application.properties files) jar包外指定了profile的配置数据文件:application-{profile}.yml jar包外的配置数据文件:application.yml jar包内指定了profile的配置数据文件:application-{profile}.yml jar包内的配置数据文件:application.yml作用在由@Configuration注解修饰的类上的@PropertySource注解,即@PropertySource annotations on your @Configuration classes;默认属性,即Default properties(specified by setting SpringApplication#setDefaultProperties)。
  Springboot在启动过程中的SpringApplication#prepareEnvironment方法中会加载上述的外部化配置为Environment,Environment是Springboot外部化配置的入口,通过Environment可以获取到Springboot加载的所有外部化配置。
  下图给出了SpringApplication#prepareEnvironment方法执行完后Environment的详细信息。
  可见Environment的实际类型为StandardServletEnvironment,这是和Springboot的应用程序类型挂钩,这点后面再说。StandardServletEnvironment内部持有一个MutablePropertySources对象,该对象持有一个PropertySource的集合,Springboot加载的每一种外部化配置都会最终被解析为一个PropertySource的实现类并存放在MutablePropertySources的PropertySource集合中,PropertySource就是每一种外部化配置源在Springboot中的体现,其提供了对外部化配置的各种操作。根据上图为例,给出一部分外部化配置源与PropertySource的实现类的对应关系。
  外部化配置
  PropertySource
  命令行参数
  SimpleCommandLinePropertySource
  JAVA系统属性
  PropertiesPropertySource
  操作系统环境变量
  OriginAwareSystemEnvironmentPropertySource
  配置数据文件
  OriginTrackedMapPropertySource
  启动程序时通过命令行指定的应用程序参数(args)会被先创建为DefaultApplicationArguments对象,然后再被解析为SimpleCommandLinePropertySource,例如通过IDEA进行如下配置。
  那么对应的SimpleCommandLinePropertySource如下所示。
  如果在resources目录创建一个application.yml文件,且内容如下。server:   port: 8080   address: 127.0.0.1
  那么对应的OriginTrackedMapPropertySource如下所示。
  下面将从SpringApplication#prepareEnvironment方法为入口,对Springboot启动流程中的外部化配置加载进行简要分析。SpringApplication#prepareEnvironment方法如下所示。private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,         DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {     // 创建ConfigurableEnvironment对象     ConfigurableEnvironment environment = getOrCreateEnvironment();     // 将命令行参数解析为PropertySource并加载到Environment中     configureEnvironment(environment, applicationArguments.getSourceArgs());     ConfigurationPropertySources.attach(environment);     // 发布Environment准备好的事件     // 进一步加载更多的外部化配置到Environment中     listeners.environmentPrepared(bootstrapContext, environment);     DefaultPropertiesPropertySource.moveToEnd(environment);     configureAdditionalProfiles(environment);     bindToSpringApplication(environment);     if (!this.isCustomEnvironment) {         environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,                 deduceEnvironmentClass());     }     ConfigurationPropertySources.attach(environment);     return environment; }
  在SpringApplication#prepareEnvironment方法中,首先会调用getOrCreateEnvironment() 方法创建ConfigurableEnvironment对象,创建出来的ConfigurableEnvironment的实际类型会根据SpringApplication初始化时推断出来的WEB应用程序类型而定,如果WEB应用程序类型为SERVLET,则创建出来的ConfigurableEnvironment实际类型为StandardServletEnvironment,并且在初始化StandardServletEnvironment时还会一并将JAVA系统属性和操作系统环境变量这两个外部化配置加载到StandardServletEnvironment中。
  在创建好StandardServletEnvironment后,会再将命令行参数解析为PropertySource并加载到StandardServletEnvironment中,随后就通过Springboot事件机制向ApplicationListener发布Environment准备好的事件,这里会接收该事件的ApplicationListener为EnvironmentPostProcessorApplicationListener(2.4.0版本以前为ConfigFileApplicationListener,该监听器从2.4.0版本起被废弃)。
  接下来先分析一下getOrCreateEnvironment() 方法的实现。private ConfigurableEnvironment getOrCreateEnvironment() {     if (this.environment != null) {         return this.environment;     }     // 根据WEB应用程序类型创建不同的ConfigurableEnvironment     switch (this.webApplicationType) {     case SERVLET:         return new StandardServletEnvironment();     case REACTIVE:         return new StandardReactiveWebEnvironment();     default:         return new StandardEnvironment();     } }
  StandardServletEnvironment的类图如下所示。
  StandardServletEnvironment在初始化时会先调用到其父类AbstractEnvironment的构造方法,如下所示。public AbstractEnvironment() {     customizePropertySources(this.propertySources); }
  实际会调用到StandardServletEnvironment实现的customizePropertySources() 方法,如下所示。protected void customizePropertySources(MutablePropertySources propertySources) {     // Servlet相关的外部化配置的加载     propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));     propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));     if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {         propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));     }     // 调用父类StandardEnvironment实现的customizePropertySources()方法     super.customizePropertySources(propertySources); }
  继续看StandardEnvironment实现的customizePropertySources() 方法,如下所示。protected void customizePropertySources(MutablePropertySources propertySources) {     // 将JAVA系统属性解析为PropertiesPropertySource,并加载到PropertySource集合中     propertySources.addLast(             new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));     // 将操作系统环境变量解析为SystemEnvironmentPropertySource,并加载到PropertySource集合中     propertySources.addLast(             new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
  到这里getOrCreateEnvironment() 方法做的事情分析完毕。
  下面再分析一下EnvironmentPostProcessorApplicationListener接收到Environment准备好的事件(ApplicationEnvironmentPreparedEvent)后的执行流程,EnvironmentPostProcessorApplicationListener的onApplicationEvent() 方法如下所示。public void onApplicationEvent(ApplicationEvent event) {     if (event instanceof ApplicationEnvironmentPreparedEvent) {         // 事件event的实际类型为ApplicationEnvironmentPreparedEvent         onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);     }     if (event instanceof ApplicationPreparedEvent) {         onApplicationPreparedEvent((ApplicationPreparedEvent) event);     }     if (event instanceof ApplicationFailedEvent) {         onApplicationFailedEvent((ApplicationFailedEvent) event);     } }
  继续看onApplicationEnvironmentPreparedEvent() 方法。private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {     ConfigurableEnvironment environment = event.getEnvironment();     SpringApplication application = event.getSpringApplication();     // 遍历所有EnvironmentPostProcessor的实现类,每个EnvironmentPostProcessor的实现类都会对相应的外部化配置做后置处理     // 处理配置数据文件的EnvironmentPostProcessor的实际类型为ConfigDataEnvironmentPostProcessor     for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {         postProcessor.postProcessEnvironment(environment, application);     } }
  EnvironmentPostProcessor的继承树如下所示。
  每一个EnvironmentPostProcessor的实现类都会对相应的外部化配置做后置处理,例如RandomValuePropertySourceEnvironmentPostProcessor会加载一个RandomValuePropertySource到Environment中,SystemEnvironmentPropertySourceEnvironmentPostProcessor会将Environment中的SystemEnvironmentPropertySource替换为SystemEnvironmentPropertySource的子类OriginAwareSystemEnvironmentPropertySource。
  在EnvironmentPostProcessor的实现类中,有一个较为重要的实现类叫做ConfigDataEnvironmentPostProcessor,其可以将配置数据文件(application.yml等)加载为OriginTrackedMapPropertySource并设置到Environment中。
  至此,Springboot启动流程中的外部化配置加载分析完毕,下面是小结。首先会根据初始化SpringApplication时推断出来的WEB应用程序类型创建不同的Environment,例如WEB应用程序类型为SERVLET时,创建的Environment的实际类型为StandardServletEnvironment;在创建StandardServletEnvironment时,就会向StandardServletEnvironment中加载一部分外部化配置,在这个阶段加载的外部化配置主要是JAVA系统属性和操作系统环境变量;在创建StandardServletEnvironment后,还会通过Springboot事件机制向EnvironmentPostProcessorApplicationListener发布ApplicationEnvironmentPreparedEvent事件,EnvironmentPostProcessorApplicationListener中收到ApplicationEnvironmentPreparedEvent事件后,会调用EnvironmentPostProcessor的实现类完成对Environment的后置处理,即会继续向Environment加载外部化配置,配置数据文件(application.yml等)的加载就在这个阶段完成;StandardServletEnvironment内部持有一个MutablePropertySources对象,该对象持有一个PropertySource的集合,Springboot加载的每一种外部化配置都会最终被解析为一个PropertySource的实现类并存放在MutablePropertySources的PropertySource集合中,PropertySource就是每一种外部化配置源在Springboot中的体现,其提供了对外部化配置的各种操作。总结
  Springboot启动时,第一件重要事件就是初始化SpringApplication,并主要完成如下事情。设置源。实际就是设置Spring容器启动时依赖的初始配置类,也就是Springboot中的启动类;设置WEB应用程序类型。例如可以是SERVLET,REACTIVE等;加载并设置Bootstrapper,ApplicationContextInitializer和ApplicationListener;设置应用程序主类的Class对象。
  然后Springboot启动时还会开启事件机制,主要就是通过运行时监听器EventPublishingRunListener创建事件并分发给对应的ApplicationListener。
  再然后会加载外部化配置,也就是得到很重要的Environment对象,通过Environment对象就可以拿到Springboot加载的所有外部化配置。
  再然后会完成容器刷新,也就是执行Spring中的各种扩展点,初始化各种bean,这部分逻辑属于是Spring的逻辑,故本文并未详细介绍。除此之外,在容器刷新时,还会完成WEB容器的启动,例如启动Springboot内嵌的Tomcat,这部分内容比较多,会在后面单独进行分析。
  最后,Springboot在整个启动流程中,会借助事件机制来发布各种事件,发布事件就是借助于上述提到的EventPublishingRunListener,这是一个运行时监听器,是Springboot中提供的监听器,不要和Spring中的ApplicationListener混淆了。
  如果觉得本篇文章对你有帮助,求求你点个赞,最后再点个关注吧。创作不易,感谢支持!
  链接:https://juejin.cn/post/7214831216028745783

从0搭建一个WebRTC,实现多房间多对多通话,并实现屏幕录制这篇文章开始会实现一个一对一WebRTC和多对多的WebRTC,以及基于屏幕共享的录制。本篇会实现信令和前端部分,信令使用fastity来搭建,前端部分使用Vue3来实现。为什么要从月薪5000到年薪1。2亿美元?假的!博士电视求职被拒并没有翻身今天看到一条消息,一位香港中文大学物理材料专业的博士刘本良,4年前在非你莫属求职,月薪要求5000元,结果被评委团拒绝和各种讽刺,后到美国研发冷核聚变,现在年薪高达1。2亿美元。你百度智能AI叶悠悠,杀人或自杀竟成可爱,恐怖昨晚偶然在网上看到,百度输入法推出了两个智能AI,能与人聊天,一个叫林开开,一个叫叶悠悠。怀着好奇的心情下载了百度输入法,登录,然后打开智能AI叶悠悠,想看看智能程度如何。聊了几句被限制后的高通,大概率面临溃不成军头条创作挑战赛被限制后的高通,大概率面临溃不成军!高通对于全球市场而言,拥有着极高的地位,可以说国产手机能够发展起来,骁龙芯片是功不可没的,不然凭借着存在缺陷的安卓系统,不可能达到5位内蒙退休大爷,结伴去贵州旅游,没买茅台,却买了这3瓶酒不知道现在有多少朋友都幻想着退休之后的生活,小编也是经常这么想,如今一日复一日,重复着简简单单的生活,每一天的两点一线游走于公司和家里,这种日子虽然平平无奇,但是也倍感单调。前不久2022世界人工智能大会开幕1聚焦2022世界人工智能大会这届AI卷出新高度!9月1日,2022世界人工智能大会开幕。在世博中心,AI作画智能文字识别AI系统和数字孪生等AI核心技术纷纷多元化呈现,吸引了不少百万车长的共同选择,坦克世界街机模式带来全新体验中伏正盛,新一轮极端高温在全国肆虐,很多地区已经突破了40度。目前尚不知高温何时会结束,大家还是做好防暑降温工作吧。空调与风扇是这个夏天的必需品,一款耐玩的游戏也非常重要。这里小编Python里IDLE的基本操作,小白级别的新手教程1。IDLE的基本操作和常用快捷键新建文件FileNewFile(快捷键为CtrlN)运行RunRunModule(快捷键为F5)保存文件FileSave(快捷键为CtrlS)运行华为Mate50有望支持卫星通信!或为6G布局9月2日,据相关爆料,即将在9月6日发布的华为Mate50系列有望支持卫星通信,华为华为常务董事终端BGCEO智能汽车解决方案BUCEO余承东在采访中爆料,华为Mate系列将在通讯东方甄选盈利超乎预期胡润2022年中全球独角兽榜公布股融易资讯今日话题东方甄选三个月带货20亿,利润率比纯做直播带货或MCN公司高据证券时报e公司报道,近日,新东方在线执行董事兼CFO尹强在2022财年业绩电话会上称,东方甄选近三个上半年中国神华净利润同比增长58。1中国神华8月27日发布的半年报显示,2022年上半年实现营业收入1655。79亿元,同比增长15归属于上市公司股东的净利润411。44亿元,同比增长58。1基本每股收益2。071元
刚刚!长沙市政府领导最新分工公布刚刚,长沙市政府门户网站更新了市政府领导成员最新工作分工情况,并明确了各自分管领域和联系部门。郑建新市委副书记市长工作分工领导市政府全面工作。负责审计工作。分管市审计局市政府研究室郎平官宣回归!62岁郎平再添新角色,薪资曝光,蔡斌夺冠加码万众瞩目的女排亚洲杯落幕,中国女排国青队决赛1比3输给日本女排,此前的女排世界联赛,蔡斌带领的国家队1比3输给日本女排,前不久的U20女排亚锦赛0比3输给日本女排,2022女排亚少陈毅去世13年后,一江西农妇说自己是陈毅的妻子,蔡畅适当照顾1985年,中国妇女杂志收到了一封来自江西农村的信,写信的人是赖月明,自称是陈毅元帅的老婆。编辑部的工作人员一脸疑惑地问道陈毅元帅的妻子不是张茜吗?这时候,陈毅已经去世13年了,可梅根夫妇无功而返,国葬结束后立刻返美,在王室早已没立锥之地梅根夫妇无功而返,国葬结束后立刻返美,在王室早已没立锥之地苏塞克斯公爵夫妇不按套路出牌,是王室最令人头疼的一对。为了继续过荣华富贵的生活,只能不断爆料和抹黑王室,毕竟只有引起大众的S29赛季初冲分潜力股推荐,又有谁吃到了版本红利?前些天,官方公布了S29赛季的开启时间,会在9月22日更新,也就是本周四。今天,我打算基于抢先服的几天体验,来和大家说说S29赛季初的几个冲分潜力股。中路潜力股海月海月是S29赛季中国女篮女排出战世界大赛,双双有望争取好成绩女篮世界杯将于9月22日10月1日在澳大利亚举行,女排世锦赛于9月23日10月15日在荷兰波兰举行。作为女排女篮的强队,中国女排女篮期待再次登上领奖台。女篮世界杯共有12支球队参加遗憾无缘中国女篮世界杯阵容,杨舒予坦言仍有宝贵收获中新网9月21日电中国女篮队员杨舒予20晚通过个人社交媒体发文称,加入中国女篮备战世界杯到现在四个月的时间,从一开始不适应到慢慢跟上球队的脚步,摸索学习一套完整的体系。我感触最大的2023年F1赛历暂行版公布!中国大奖赛时隔四年回归央视网消息昨日晚间,国际汽联公布了2023年F1赛历暂行版。中国大奖赛在列,美国有3站意大利2站。2023年F1全年共有24站比赛,中国大奖赛位列第4站,正赛于4月16日举行。这是未来几个月中国空间站工程还将实施3次重大发射任务央视网消息星空浩瀚,中国航天的探索也始终步履不停。今年是空间站全面建成投入运营之年。本月底,中国空间站问天实验舱将通过转位机械臂调整到永久停泊口。此时,问天实验舱与天和核心舱就形成巡天守护三十年生命之塔助中国航天行稳致远图为逃逸塔转运现场。航天科技四院供图中新网西安9月21日电(杨英琦)2022年9月21日,中国载人航天工程立项已三十周年。三十年来,历经上万个日夜和数百次的试验,中国航天员的生命之羊了个羊大火的爆款逻辑,看懂了你才能赚到羊了个羊消除小游戏最近这么火,我一直在犹豫说还是不说。随便说说吧,就是蹭热点,但是不说吧,又总觉得这里蕴藏很大的价值宝藏,就这么放过,很可惜!内行看门道,外行看热闹。那么,通过羊了