Springboot | 启动 - [00 准备环境]

§1 new SpringApplication

图片

图片

图 1-1 SpringApplication.run() 的实际逻辑
图片

这里先关注上图 <1> 的部分,对应的代码如下

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //0,设置主启动类
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //1,推断网络应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
    //2,设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //3,设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 
    this.mainApplicationClass = deduceMainApplicationClass();
}

入参

  • resourceLoader:当前是 null
  • primarySources:主类,就是当前应用的 Application 类的 class

    如果项目叫 demo,这里就是 DemoApplication.class

主要逻辑

//1 推断网络应用类型

springboot 定义了 3 种类型

类型 含义 判断条件
NONE 非 web 应用
不启动内嵌 web 服务器
不能加载到 javax.servlet.Servlet 和
org.springframework.web.context.ConfigurableWebApplicationContext
SERVLET 基于 serverlet 的网络应用 除 NONE / REACTIVE 的其他情况
REACTIVE 响应式网络应用
内嵌 reactive web 服务器
可以加载 org.springframework.web.reactive.DispatcherHandler
且不能加载到 org.springframework.web.servlet.DispatcherServlet 和
org.glassfish.jersey.servlet.ServletContainer

//2 设置初始化器

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    //2-1,从所有 META-INF/spring.factories 里获取初始化器的实现类名(如下图 2-1),扔到 set 里去重
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //2-2,实例化,里面核心逻辑就是 Class.forName 获取 Class,然后进行实例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    //2-3,排序,先按实现的 Ordered 接口,后按 @Order 注解,最后按装饰类的 @Order 注解排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

图 2-1 META-INF/spring.factories 里的初始化器实现类名
图片

//3 设置监听器

与 //2 同理,代码也是同一段
只不过是从 META-INF/spring.factories 里加载 org.springframework.context.ApplicationListener,类似下面这些
图片

§2 run

现在关注最上面图 1-1 中 <2> 的部分,整个 run() 如下

//0,启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//1,设置无头模式
configureHeadlessProperty();
//2,整理 SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
   ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   //3,准备环境
   ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
   configureIgnoreBeanInfo(environment);
   Banner printedBanner = printBanner(environment);
   context = createApplicationContext();
   prepareContext(context, environment, listeners, applicationArguments, printedBanner);
   refreshContext(context);
   afterRefresh(context, applicationArguments);
   stopWatch.stop();
   //0-1,停止计时
   if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
   }
   listeners.started(context);
   callRunners(context, applicationArguments);
}
catch (Throwable ex) {
   handleRunFailure(context, ex, listeners);
   throw new IllegalStateException(ex);
}

try {
   listeners.running(context);
}
catch (Throwable ex) {
   handleRunFailure(context, ex, null);
   throw new IllegalStateException(ex);
}
return context;

//0 启动计时器

一个简单的计时器,在 //0-1 处停止并完成计时,最终用于打印如下的日志
下面的两个时间,前者是 当前计时器时长,后者是 jvm上电时长

Started xxApplication in 6.59 seconds (JVM running for 7.59)

同时,从 //0-1 处代码可知:
上述日志的打印,不意味着项目启动完成,只是 spring 容器启动完成 了,在这之后,spring 还需要启动

  • SpringApplicationRunListener
  • ApplicationRunnerCommandLineRunner

//1 设置无头模式

springboot 项目基本都运行在服务器上,因此通常设置其工作于 <无头模式> 下,即无显示器、键盘、鼠标
其核心其实就是下面这句

System.setProperty("java.awt.headless", "true");

//2 整理 SpringApplicationRunListener

和上文 设置初始化器 处用的同一个方法,这次是为了加载 org.springframework.boot.SpringApplicationRunListener
但实际只加载到 EventPublishingRunListener,如下图

前面(下图)的是 org.springframework.context.ApplicationListener,用于处理 spring 的事件,不同的实现中会过滤当前实现关注的事件处理
现在(下下图)的是 org.springframework.boot.SpringApplicationRunListener,用于处理 springboot 的事件

图片

图片

SpringApplicationRunListener 说是个 listener,但其接口与实现实际更像是个 publisher,接口中方法如下
观察其中方法,对比上文中 run() 中逻辑,可见整体脉络大约是能对上的

public interface SpringApplicationRunListener {
    default void starting() {}
    default void environmentPrepared(ConfigurableEnvironment environment) {}
    default void contextPrepared(ConfigurableApplicationContext context) {}
    default void contextLoaded(ConfigurableApplicationContext context) {}
    default void started(ConfigurableApplicationContext context) {}
    default void running(ConfigurableApplicationContext context) {}
    default void failed(ConfigurableApplicationContext context, Throwable exception) {}
}

SpringApplicationRunListener 有一个唯一的默认实现 EventPublishingRunListener
其核心属性是 SimpleApplicationEventMulticaster
其对 SpringApplicationRunListener 中各个方法的实现,也是用这个 Multicaster 去广播对应的事件

// 下面的代码是 EventPublishingRunListener 中的一部分 
// EventPublishingRunListener 是 SpringApplicationRunListener 的实现

// EventPublishingRunListener 的构造器,可见其核心属性 initialMulticaster 的定义
public EventPublishingRunListener(SpringApplication application, String[] args) {
    // ... 前面的不重要
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    // ... 后面的也不重要
}

// EventPublishingRunListener 中的 starting() 方法实现
// 其逻辑就是广播了 ApplicationStartingEvent 事件
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

SpringApplicationRunListener 的接口是 ApplicationEventMulticaster,核心方法 multicastEvent() 的主要逻辑也在下面简要说明

/**************************************
 * 实现此接口的对象,可以管理若干 ApplicationListener 对象并向它们发布事件
 * ApplicationEventPublisher 可以委派此接口的对象为代表,在实际上代其发布事件
 * 这里说的 ApplicationEventPublisher 通常是 org.springframework.context.ApplicationContext
 **************************************/
public interface ApplicationEventMulticaster {}
/**************************************
 * ApplicationEventMulticaster 接口的简单实现
 * 
 * 此实现会广播所有事件给 listeners,这里的 listener 是指 ApplicationListener
 * 如果 listener 对广播的事件不感兴趣,它会自己忽略对应的事件
 * 一般情况下,listener 会对传入的事件进行 instanceof 检查,这决定了它是否对此事件感兴趣
 *
 * 默认情况下,所有 listener 都在调用线程中执行。
 * 如果有个无赖 listener(应该指的是处理很慢的监听器),就会有阻塞整个应用的风险
 * 但这个方式具有最小的开销(这个实现较 simple 应该就是因为这个原因)
 * 也可以指定一个替代的替代的任务执行器,比如一个线程池,这样可以使 listener 执行与不同的线程
 **************************************/
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    // 整个过程只需要关注两个步骤,见下面注释
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        // 1 按事件类型筛选 listener
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
           if (executor != null) {
              executor.execute(() -> invokeListener(listener, event));
           }
           else {
              // 2 依次调用它们处理事件
              // 上面注释中的说的 "调用线程中执行" 说的就是这里:一个 for 循环就直接调用了
              // 实际就是走了这句话 listener.onApplicationEvent(event)
              invokeListener(listener, event);
           }
        }
    }
}

这个话题暂时看到这里就可以了,继续按 run() 的逻辑向下

//3 准备环境

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
    //3-1,创建 env 实例,会根据前面的网络应用类型创建不同的实例
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //3-2,配置环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    //3-3,环境依附
    ConfigurationPropertySources.attach(environment);
    //3-4,环境预备
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
       environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
             deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

//3-1 创建 env 实例

getOrCreateEnvironment() 会根据网络应用类型创建对应的 environment 实例,大部分情况下是 StandardServletEnvironment
StandardServletEnvironment 比较特殊,其构造器在父类 AbstractEnvironment
而其逻辑又被委托给了一个待实际实现的方法 customizePropertySources()

public AbstractEnvironment() {
    customizePropertySources(this.propertySources);
}
protected void customizePropertySources(MutablePropertySources propertySources) {}

此方法经历了两级扩展,这两级扩展一共为最终的 StandardServletEnvironment 增加了 4 个配置源

// StandardServletEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // 增加配置源 servletConfigInitParams
    propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
    // 增加配置源 servletContextInitParams
    propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
    if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {// JNDI 不需要关注,J2EE 的规范,但用的不宽泛
       propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
    }
    super.customizePropertySources(propertySources);
}

// StandardEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
    // 增加配置源 systemProperties
    propertySources.addLast(
          new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
    // 增加配置源 systemEnvironment
    propertySources.addLast(
          new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

//3-2 配置环境

configureEnvironment() 可以理解为对上面创建的 environment 实例本身 进行配置,而不是通过向其中增加配置而达到配置项目的目的,代码如下

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

configureEnvironment() 整体上是一个模板方法,其中的逻辑主要分两个部分,分别由两个方法实现

  • configurePropertySources(),配置 environment 的属性源(即 propertySource)

    • 此阶段,spring 容器可以加载到 4 个源(就是上面那 4 个),有 2 个有具体属性,里面是环境参数和系统属性
    • 此方法会在满足条件的情况下额外添加 2 种源
      • defaultProperties:默认属性,如果创建 SpringApplication 的父子容器,则源容器中的系统和环境属性会以此形式作为源
      • commandLineProperties:命令行参数,即下图的配置(不是 vm options),如果有实际值则会作为源
        图片
        图片
  • configureProfiles(),配置激活的 profile,spring.profiles.active 属性即在此生效

//3-3 环境依附

对指定的 environment(就是之前创建的那个)附加对 ConfigurationPropertySource 的支持
被 environment 管理的每个配置源(PropertySource)都会适配一个 ConfigurationPropertySource
ConfigurationPropertySource 允许经典的 PropertySourcesPropertyResolver 按配置属性名称调用解析 PropertySource

其原理大约如下

  • 从 environment 里获取所有 PropertySource
  • 清除历史 attach 的结果(可以回去看一眼 prepareEnvironment() 的代码,此步骤调用了两次)
  • 进行当前 attach

将前面获取的 PropertySource 列表封装为 ConfigurationPropertySourcesPropertySource 对象
并把这个新对象本身增加到原 PropertySource 列表的第一个元素

public static void attach(Environment environment) {
// 前面不重要,sources 就是从 environment 中获取的 PropertySource
    sources.addFirst(
       new ConfigurationPropertySourcesPropertySource(
           ATTACHED_PROPERTY_SOURCE_NAME,new SpringConfigurationPropertySources(sources)
       )
    );
}

ConfigurationPropertySourcesPropertySource 提供按属性名获取属性的能力
SpringConfigurationPropertySources 提供了实际的 adapt() 能力,其实际作用是将
PropertySource 转变为 ConfigurationPropertySource 并建立二者的映射,然后
ConfigurationPropertySourcesPropertySource 的能力就有用武之地了

//3-4 环境预备

就是为了执行下面这句话,其目的是向 spring 的 listener 广播 ApplicationEnvironmentPreparedEvent

this.initialMulticaster
       .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));

上文已经简单的串过 multicastEvent() 的浅层逻辑:
按事件类型过滤所有的 ApplicationListener,并将事件广播过去,然后被各自的 onApplicationEvent() 处理
现在继续深入 onApplicationEvent(),先来看一眼都有哪些 ApplicationListener 响应了这个事件

图片

  • ConfigFileApplicationListener

从已知位置加载 properties 来配置上下文环境的 EnvironmentPostProcessor,先不需要关注这个类型是啥
默认的配置就是熟知的 application.properties/application.yml,已知位置(按优先级)即

  • file:./ config/
  • file:./ config/*/
  • file:./
  • classpath:config/
  • classpath:

基于 spring.profiles.active 的附加配置文件也会被加载

  • AnsiOutputApplicationListener

spring.output.ansi.enabled 配置 AnsiOutput,此配置开启后,可以通过输出 ANSI 转移序列来给应用的输出着色

  • LoggingApplicationListener

用于配置日志系统的
此 listener 优先按 environment 中包含的 logging.config启动日志系统,如果没有就按默认配置启动

  • ClasspathLoggingApplicationListener

用于响应环境就绪事件/环境失败事件,并以 DEBUG 级别日志记录线程上下文类加载器(TCCL)的类路径
其实就是在 DEBUG 日志开启时,打印 Application started with classpath: 日志

  • BackgroundPreinitializer

用于触发预初始化,此预初始化是一个比较耗时的过程,所以在后台线程中进行
包括 spring 的默认转换器、格式化器、消息转换器(http相关),javax.validation,json,编码集的预初始化
预初始化,有的是向容器中增加示例,有的仅仅是调用相关类以保证类的加载

  • WelcomeLogoApplicationListener(忽略,这是 dubbo 引入的)
  • DelegatingApplicationListener

这是一个扩展点,它会把事件的处理委派个其他 lisenter
可以通过 context.listener.classes 配置,决定把事件委派给哪些 lisenter

  • FileEncodingApplicationListener

用于当系统编码值与设置在 environment 里的预期值不符时,终止应用启动
默认情况下不生效,但当设置了 spring.mandatory_file_encoding 且与系统属性 file.encoding 不一致时就会抛出异常

上述 ApplicationListener 中,我们先只关注其中的两个,他们分别负责两个重要的过程

  • ConfigFileApplicationListener ,它负责默认配置文件的加载
  • LoggingApplicationListener,它负责日志系统的启动

ConfigFileApplicationListener

入口的事件校验
此 listener 中 onApplicationEvent() 的实现长下图这样
不仅是此 Listener,基本所有 ApplicationListener 都有此逻辑,这就是 SpringApplicationRunListener 注释中所谓的 instanceof 检查

public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationEnvironmentPreparedEvent) {
       onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
    // 后面还有一个 if,用于处理另一个事件
}

实际处理逻辑的代码如下

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
    //3-4-1 EnvironmentPostProcessor
    //3-4-2 准备 EnvironmentPostProcessor 工作链
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);
    //3-4-3 驱动执行 EnvironmentPostProcessor 链
    for (EnvironmentPostProcessor postProcessor : postProcessors) {
       postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
}

//3-4-1 关于 EnvironmentPostProcessor
springboot 中有很多 PostProcessor,比如 BeanPostProcessor,直译的话可以称呼他们为 <XX 后置处理器>
所谓后置处理器,可以理解是 实例化的后置,即:<XX 后置处理器> 是在 XX 实例化之后、实际投入使用之前触发执行的,对 XX 实现定制化的处理器
因此,EnvironmentPostProcessor 是在实例化后定制Environment 的处理器,和当前进度吻合,其接口定义与注释如下:

/**************************************
 * 允许在应用上下文 refreshed 之前,定制 Environment(的处理)
 * 此接口的实现必须注册在 META-INF/spring.factories
 * 鼓励在此接口实现执行之前,先检查是否实现 Ordered 接口或带有 @Order 注解,并基于此排序
 **************************************/
public interface EnvironmentPostProcessor {
    void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}

诶,又提到了 spring.factories
不妨按接口上的注释,尝试增加一个 EnvironmentPostProcessor 试试

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
public class BbyijuEnvironmentPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        System.out.println("我就 BB 一句");
    }
}

resources/META-INF/spring.factories

org.springframework.boot.env.EnvironmentPostProcessor=\
com.a.b.c.BbyijuEnvironmentPostProcessor

启动验证,可以看到他确实生效了(整理到这的进度,还没到打印 banner 呢……)
图片

//3-4-2 准备工作链
回到 ConfigFileApplicationListener 的处理逻辑,如果忘了可以回 这里 看一眼,暂时先关注下面三句

//3-4-2 准备工作链
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);

结合 EnvironmentPostProcessorspring.factories 机制
不难猜 loadPostProcessors() 又是走的 loadFactories() 方法(或 loadFactoryNames()),果然

List<EnvironmentPostProcessor> loadPostProcessors() {
    return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}

这样得到了所有 EnvironmentPostProcessor 实例,然后将 ConfigFileApplicationListener 自身加入实例列表
最后对所有实例进行排序,这个排序方法也是老朋友,忘了的同学参考 <//2 设置初始化器>,最后得到的就是最终的工作链

这里的写法可以看做是一个变种的简化责任链,其设计目的就是自己确定核心,同时开发定制化能力给用户

  • springboot 将加载配置文件的核心逻辑在自己定义的 ConfigFileApplicationListener 中进行了实现
  • 同时希望留下足够的可扩展性供用户自定义,这个可扩展性的实现,就交由 SPI 去实现
  • SPI 需要接口识别,于是 springboot 定义了 EnvironmentPostProcessor 接口,其实现是对 ConfigFileApplicationListener 的扩展和补充

    换句话说,ConfigFileApplicationListener 和这些实现其实是同一类东西,因此 ConfigFileApplicationListener 也需要实现此接口

  • 定制化的扩展要求某个扩展在谁前面、在谁后面,那都是合理的需求,因此需要排序来确定工作的先后顺序

    其实就算忽略设计意义,仅从编码的角度,ConfigFileApplicationListener 也得实现 EnvironmentPostProcessor
    否则和其他的扩展组件都不保持队形,那怎么参与这里的排序,更后面怎么被一个 for 循环驱动执行

在上述步骤执行后,最终的工作链如下图,简单串一下它们的作用
图片

  • SystemEnvironmentPropertySourceEnvironmentPostProcessor

environment 里目前有 6 个 propertySource
附带一嘴:propertySource 一开始有 4 个,然后 <配置环境> 阶段增加了一个命令行来源 SimpleCommandLinePropertySource,接着 <环境依附> 阶段又增加了一个用属性名来访问属性的 propertySource,即 ConfigurationPropertySourcesPropertySource
一开始的 4 个里,有个叫 systemEnvironment、类型为 SystemEnvironmentPropertySource 系统环境配置源,没有印象的同学看 <配置环境> 里的截图
EnvironmentPostProcessor,就是为了把它替换成 OriginAwareSystemEnvironmentPropertySource
这是 SystemEnvironmentPropertySource 的子类(原本类型的子类),目的是增加其跟踪每个系统环境属性 SystemEnvironmentOrigin 的能力

  • SpringApplicationJsonEnvironmentPostProcessor

使 environment 增加对 json 格式配置源的支持,其实就是下图这种写法(或一个 .json 文件)
图片
这会使 environment 增加一个叫 spring.application.jsonpropertySource

  • CloudFoundryVcapEnvironmentPostProcessor

如果应用运行于 VCAP 环境,此 EnvironmentPostProcessor 用于找到并解析其元数据,基本需要关注
VCAP = vmware's cloud application platform(应该是这个),即目前的 Cloud Foundry(开源云应用 PaaS 平台)

  • ConfigFileApplicationListener

本尊,后文细说

  • BbyijuEnvironmentPostProcessor

咱自己加的那个用来 "我就 BB 一句" 的那个,不需要关注

  • DebugAgentEnvironmentPostProcessor

是否允许 java Reactor 的异步调试工具
此参数疑似在生产环境中建议关闭,待确认

  • DubboDefaultPropertiesEnvironmentPostProcessor

dubbo 的,当前场景不需要关注

//3-4-3 驱动执行
回到 ConfigFileApplicationListener 的处理流程
从上文,ConfigFileApplicationListener 已经组装好了最终的工作链,这里就开始驱动它们执行了,相关代码如下
说白了就是开个循环,调用每一个 EnvironmentPostProcessorpostProcessEnvironment()ConfigFileApplicationListener 也是其一

//3-4-3 驱动执行
for (EnvironmentPostProcessor postProcessor : postProcessors) {
   postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}

众多 EnvironmentPostProcessor 中,暂时只关注 ConfigFileApplicationListener 自身的细节,非细节的部分上文已经简单的串过
这个步骤会涉及 springboot 读取配置文件的逻辑,另开新帖单独说明

LoggingApplicationListener

posted @ 2025-08-28 15:22  问仙长何方蓬莱  阅读(7)  评论(0)    收藏  举报