SpringBoot--SpringApplication.run执行流程详解

本文深入探讨SpringBoot应用启动流程,解析SpringApplication如何通过一系列扩展点实现应用的启动与关闭,包括SpringApplicationRunListener、ApplicationListener等关键组件的作用。
摘要由CSDN通过智能技术生成

SpringApplication 将一个典型的 Spring 应用启动的流程“模板化”(这里是动词),在没有特殊需求的情况下,默认模板化后的执行流程就可以满足需求了但有特殊需求也没关系,SpringApplication 在合适的流程结点开放了一系列不同类型的扩展点,我们可以通过这些扩展点对 SpringBoot 程序的启动和关闭过程进行扩展。

最“肤浅”的扩展或者配置是 SpringApplication 通过一系列设置方法(setters)开放的定制方式,比如,我们之前的启动类的 main 方法中只有一句:

SpringApplication.run(DemoApplication.class,args);

但如果我们想通过 SpringApplication 的一系列设置方法来扩展启动行为,则可以用如下方式进行:

public class DemoApplication {
    public static void main(String[] args) {
        // SpringApplication.run(DemoConfiguration.class, args);
        SpringApplication bootstrap = new SpringApplication(Demo - Configuration.class);
        bootstrap.setBanner(new Banner() {
            @Override
            public void printBanner(Environment environment, Class<?> aClass, PrintStream printStream) {
                // 比如打印一个我们喜欢的ASCII Arts字符画
            }
        });
        bootstrap.setBannerMode(Banner.Mode.CONSOLE);
        // 其他定制设置...
        bootstrap.run(args);
    }
}

设置自定义 banner 最简单的方式其实是把 ASCII Art 字符画放到一个资源文件,然后通过 ResourceBanner 来加载:

bootstrap.setBanner(new ResourceBanner(new ClassPathResource("banner.txt")));

大部分情况下,SpringApplication 已经提供了很好的默认设置,所以,我们不再对这些表层进行探究了,因为对表层之下的东西进行探究才是我们的最终目的。

深入探索 SpringApplication 执行流程

SpringApplication 的 run 方法的实现是我们本次旅程的主要线路,该方法的主要流程大体可以归纳如下:

1)如果我们使用的是 SpringApplication 的静态 run 方法,那么,这个方法里面首先需要创建一个 SpringApplication 对象实例,然后调用这个创建好的 SpringApplication 的实例 run方 法。在 SpringApplication 实例初始化的时候,它会提前做几件事情:

根据 classpath 里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为 Web 应用使用的 ApplicationContext 类型,还是应该创建一个标准 Standalone 应用使用的 ApplicationContext 类型。

使用 SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationContextInitializer。

使用 SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationListener。

推断并设置 main 方法的定义类。

2)SpringApplication 实例初始化完成并且完成设置后,就开始执行 run 方法的逻辑了,方法执行伊始,首先遍历执行所有通过 SpringFactoriesLoader 可以查找到并加载的 SpringApplicationRunListener,调用它们的 started() 方法,告诉这些 SpringApplicationRunListener,“嘿,SpringBoot 应用要开始执行咯!”。

3)创建并配置当前 SpringBoot 应用将要使用的 Environment(包括配置要使用的 PropertySource 以及 Profile)。

4)遍历调用所有 SpringApplicationRunListener 的 environmentPrepared()的方法,告诉它们:“当前 SpringBoot 应用使用的 Environment 准备好咯!”。

5)如果 SpringApplication的showBanner 属性被设置为 true,则打印 banner(SpringBoot 1.3.x版本,这里应该是基于 Banner.Mode 决定 banner 的打印行为)。这一步的逻辑其实可以不关心,我认为唯一的用途就是“好玩”(Just For Fun)。

6)根据用户是否明确设置了applicationContextClass 类型以及初始化阶段的推断结果,决定该为当前 SpringBoot 应用创建什么类型的 ApplicationContext 并创建完成,然后根据条件决定是否添加 ShutdownHook,决定是否使用自定义的 BeanNameGenerator,决定是否使用自定义的 ResourceLoader,当然,最重要的,将之前准备好的 Environment 设置给创建好的 ApplicationContext 使用。

7)ApplicationContext 创建好之后,SpringApplication 会再次借助 Spring-FactoriesLoader,查找并加载 classpath 中所有可用的 ApplicationContext-Initializer,然后遍历调用这些 ApplicationContextInitializer 的 initialize(applicationContext)方法来对已经创建好的 ApplicationContext 进行进一步的处理。

8)遍历调用所有 SpringApplicationRunListener 的 contextPrepared()方法,通知它们:“SpringBoot 应用使用的 ApplicationContext 准备好啦!”

9)最核心的一步,将之前通过 @EnableAutoConfiguration 获取的所有配置以及其他形式的 IoC 容器配置加载到已经准备完毕的 ApplicationContext。

10)遍历调用所有 SpringApplicationRunListener 的 contextLoaded() 方法,告知所有 SpringApplicationRunListener,ApplicationContext “装填完毕”!

11)调用 ApplicationContext 的 refresh() 方法,完成 IoC 容器可用的最后一道工序。

12)查找当前 ApplicationContext 中是否注册有 CommandLineRunner,如果有,则遍历执行它们。

13)正常情况下,遍历执行 SpringApplicationRunListener 的 finished() 方法,告知它们:“搞定!”。(如果整个过程出现异常,则依然调用所有 SpringApplicationRunListener 的 finished() 方法,只不过这种情况下会将异常信息一并传入处理)。

至此,一个完整的 SpringBoot 应用启动完毕!

整个过程看起来冗长无比,但其实很多都是一些事件通知的扩展点,如果我们将这些逻辑暂时忽略,那么,其实整个 SpringBoot 应用启动的逻辑就可以压缩到极其精简的几步,如图 1 所示。

前后对比我们就可以发现,其实 SpringApplication 提供的这些各类扩展点近乎“喧宾夺主”,占据了一个 Spring 应用启动逻辑的大部分“江山”,除了初始化并准备好 ApplicationContext,剩下的大部分工作都是通过这些扩展点完成的,所以,我们接下来对各类扩展点进行逐一剖析。

SpringApplicationRunListener

SpringApplicationRunListener 是一个只有 SpringBoot 应用的 main 方法执行过程中接收不同执行时点事件通知的监听者:

public interface SpringApplicationRunListener {
    void started();
    void environmentPrepared(ConfigurableEnvironment environment);
    void contextPrepared(ConfigurableApplicationContext context);
    void contextLoaded(ConfigurableApplicationContext context);
    void finished(ConfigurableApplicationContext context, Throwable exception);
}

对于我们来说,基本没什么常见的场景需要自己实现一个 Spring-ApplicationRunListener,即使 SpringBoot 默认也只是实现了一个 org.spring-framework.boot.context.event.EventPublishingRunListener,用于在 SpringBoot 启动的不同时点发布不同的应用事件类型(ApplicationEvent),如果有哪些 ApplicationListener 对这些应用事件感兴趣,则可以接收并处理。

假设我们真的有场景需要自定义一个 SpringApplicationRunListener 实现,那么有一点需要注意,即任何一个 SpringApplicationRunListener 实现类的构造方法(Constructor)需要有两个构造参数,一个构造参数的类型就是我们的 org.springframework.boot.SpringApplication,另外一个就是 args 参数列表的 String[]:

public class DemoSpringApplicationRunListener implements SpringApplicationRunListener {

    @Override
    public void started() {
        // do whatever you want to do
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        // do whatever you want to do
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        // do whatever you want to do
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        // do whatever you want to do
    }

    @Override
    public void finished(ConfigurableApplicationContext context, Throwable exception) {
        // do whatever you want to do

    }
}

之后,我们可以通过 SpringFactoriesLoader 立下的规矩,在当前 SpringBoot 应用的 classpath 下的 META-INF/spring.factories 文件中进行类似如下的配置:

org.springframework.boot.SpringApplicationRunListener=\com.keevol.springboot.demo.DemoSpringApplicationRunListener

然后 SpringApplication 就会在运行的时候调用它啦!

ApplicationListener

ApplicationListener 其实是老面孔,属于 Spring 框架对 Java 中实现的监听者模式的一种框架实现,这里唯一值得着重强调的是,对于初次接触 SpringBoot,但对 Spring 框架本身又没有过多接触的开发者来说,可能会将这个名字与 SpringApplicationRunListener 混淆。

关于 ApplicationListener 我们就不做过多介绍了,如果感兴趣,请参考 Spring 框架相关的资料和书籍。

如果我们要为 SpringBoot 应用添加自定义的 ApplicationListener,有两种方式:

  • 通过 SpringApplication.addListeners(…)或者 SpringApplication.setListeners(…)方法添加一个或者多个自定义的 ApplicationListener。
  • 借助 SpringFactoriesLoader 机制,在 META-INF/spring.factories 文件中添加配置(以下代码是为 SpringBoot 默认注册的 ApplicationListener 配置)。
org.springframework.context.ApplicationListener=
\org.springframework.boot.builder.ParentContextCloserApplicationListener,
\org.springframework.boot.cloudfoundry.VcapApplicationListener,
\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.liquibase.LiquibaseServiceLocatorApplicat-ionListener,
\org.springframework.boot.logging.ClasspathLoggingApplicationListener,
\org.springframework.boot.logging.LoggingApplicationListener
ApplicationContextInitializer

ApplicationContextInitializer 也是 Spring 框架原有的概念,这个类的主要目的就是在 ConfigurableApplicationContext 类型(或者子类型)的 ApplicationContext 做 refresh 之前,允许我们对 ConfigurableApplicationContext 的实例做进一步的设置或者处理。

实现一个 ApplicationContextInitializer 很简单,因为它只有一个方法需要实现:

public class DemoApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // do whatever you want with applicationContext,
        // e.g.
        applicationContext.registerShutdownHook();
    }
}

不过,一般情况下我们基本不会需要自定义一个 ApplicationContext-Initializer,即使 SpringBoot 框架默认也只是注册了三个实现:

org.springframework.context.ApplicationContextInitializer=
\org.springframework.boot.context.ConfigurationWarningsApplication-ContextInitializer,
\org.springframework.boot.context.ContextIdApplicationContextInitia-lizer,
\org.springframework.boot.context.config.DelegatingApplicationContex-tInitializer

如果我们真的需要自定义一个 ApplicationContextInitializer,那么只要像上面这样,通过 SpringFactoriesLoader 机制进行配置,或者通过 SpringApplication.addInitializers(…)设置即可。

CommandLineRunner

CommandLineRunner 是很好的扩展接口,不是 Spring 框架原有的“宝贝”,它属于 SpringBoot 应用特定的回调扩展接口。源码如下所示:

public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

CommandLineRunner 需要大家关注的其实就两点:

1)所有 CommandLineRunner 的执行时点在 SpringBoot 应用的 Application-Context 完全初始化开始工作之后(可以认为是 main 方法执行完成之前最后一步)。

2)只要存在于当前 SpringBoot 应用的 ApplicationContext 中的任何 Command-LineRunner,都会被加载执行(不管你是手动注册这个 CommandLineRunner 到 IoC 容器,还是自动扫描进去的)。

与其他几个扩展点接口类型相似,建议 CommandLineRunner 的实现类使用 @org.springframework.core.annotation.Order 进行标注或者实现 org.springframework.core.Ordered 接口,便于对它们的执行顺序进行调整,这其实十分重要,我们不希望顺序不当的 CommandLineRunner 实现类阻塞了后面其他 CommandLineRunner 的执行。

<think>好的,我现在需要帮助用户理解从`SpringApplication.run(Application.class, args);`开始的Spring Boot启动流程。用户之前已经询问过Bean的创建、初始化和赋值的源码,现在他们可能想进一步了解整个应用的启动过程,特别是Spring Boot是如何启动并初始化应用的。 首先,我需要回忆一下Spring Boot的启动流程。记得`SpringApplication.run()`是入口方法,它会触发自动配置、上下文初始化、Bean加载等步骤。用户可能对底层机制感兴趣,比如如何加载配置、如何启动内嵌服务器,或者整个生命周期中的关键步骤。 接下来,我需要确定用户的背景。他们可能是一个开发者,正在学习Spring Boot,或者在工作中需要调试启动问题。因此,我需要详细但清晰地解释每个阶段,包括关键类和方法,同时避免过于晦涩的术语。 我需要组织回答的结构。可能按照启动流程的顺序,从创建SpringApplication实例开始,到准备环境、创建上下文、刷新上下文,最后启动内嵌服务器。每个步骤要说明做了什么,涉及哪些核心类,以及它们的作用。 还要注意用户之前的提问中提到了源码解析,所以这次回答可能需要引用具体的源码位置,比如`SpringApplication`类的构造方法、`run()`方法的具体实现,以及`AbstractApplicationContext`的`refresh()`方法。这能帮助用户更深入地理解内部机制。 另外,用户可能对自动配置的原理感兴趣,比如`@SpringBootApplication`背后的机制,如何加载`spring.factories`,以及条件注解如何工作。这部分需要简要提及,但不要过于深入,以免偏离启动流程的主线。 还需要考虑性能优化和常见问题,比如启动时的监听器、事件机制,或者如何自定义启动过程。但根据用户当前的问题,可能暂时不需要展开这些,除非用户后续有更多问题。 最后,我应该使用图表或流程图来辅助说明,比如Mermaid的流程图,帮助用户可视化整个流程。同时,代码示例和源码位置的引用能增加回答的可信度和实用性。 总结一下,回答的结构应该是: 1. 入口方法`run()`的作用和两个阶段。 2. 详细分解每个阶段,包括实例创建、环境准备、上下文创建和刷新。 3. 关键扩展点,如监听器、自动配置、Bean加载。 4. 图示和源码引用,帮助用户理解。 5. 可能的后续步骤或相关知识点,引导用户进一步学习。 需要确保语言简洁,逻辑清晰,逐步引导用户理解整个启动过程。同时,注意使用中文术语,避免直译导致的歧义。</think>以下是Spring Boot应用启动过程的核心源码解析(基于Spring Boot 2.5.x版本): --- ### 一、启动流程全景图 ```mermaid graph TD A[run()入口] --> B[创建SpringApplication实例] B --> C[执行run方法] C --> D[准备环境] D --> E[创建应用上下文] E --> F[刷新上下文] F --> G[执行Runners] ``` --- ### 二、启动源码深度解析 #### 1. 入口方法`SpringApplication.run()` **源码位置**:`SpringApplication.java` ```java 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); // 分两个阶段 } ``` --- ### 三、SpringApplication实例化阶段 #### 1. 构造方法核心逻辑 ```java public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { // 1. 设置主配置类 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 2. 推断WEB应用类型(Servlet/Reactive) this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 3. 加载初始化器(META-INF/spring.factories) setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 4. 加载监听器(关键事件监听) setListeners(getSpringFactoriesInstances(ApplicationListener.class)); // 5. 推断主启动类 this.mainApplicationClass = deduceMainApplicationClass(); } ``` --- ### 四、run()方法执行阶段 #### 1. 完整执行流程 **源码位置**:`SpringApplication#run` ```java public ConfigurableApplicationContext run(String... args) { // 阶段1:启动准备 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); // 获取运行监听器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); // 发送ApplicationStartingEvent try { // 阶段2:环境准备 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 阶段3:创建上下文 context = createApplicationContext(); // 阶段4:上下文预处理 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 阶段5:刷新上下文(核心!) refreshContext(context); // 阶段6:后置处理 afterRefresh(context, applicationArguments); stopWatch.stop(); // 发送ApplicationReadyEvent listeners.started(context); } catch (Throwable ex) { handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } return context; } ``` --- ### 五、关键阶段详解 #### 1. 环境准备(prepareEnvironment) ```java private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 创建环境(根据WEB类型创建对应环境) ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置PropertySources和Profiles configureEnvironment(environment, applicationArguments.getSourceArgs()); // 发送EnvironmentPreparedEvent listeners.environmentPrepared(environment); // 绑定Spring Boot配置(将环境变量绑定到SpringBootApplication对象) bindToSpringApplication(environment); return environment; } ``` #### 2. 上下文创建(createApplicationContext) ```java protected ConfigurableApplicationContext createApplicationContext() { // 根据webApplicationType选择上下文类型 return this.applicationContextFactory.create(this.webApplicationType); } // 默认实现(AnnotationConfigServletWebServerApplicationContext等) static { ApplicationContextFactory DEFAULT = (webApplicationType) -> { switch (webApplicationType) { case SERVLET: return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); } }; } ``` #### 3. 上下文刷新(refreshContext) **核心源码位置**:`AbstractApplicationContext#refresh` ```java public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备刷新(记录启动时间、初始化属性源等) prepareRefresh(); // 2. 获取新的BeanFactory(重要!) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3. BeanFactory预处理(注册环境相关的Bean) prepareBeanFactory(beanFactory); // 4. BeanFactory后处理(加载配置类、组件扫描等) postProcessBeanFactory(beanFactory); // 5. 执行BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 6. 注册BeanPostProcessor registerBeanPostProcessors(beanFactory); // 7. 初始化MessageSource(国际化) initMessageSource(); // 8. 初始化事件广播器 initApplicationEventMulticaster(); // 9. 子类扩展方法(如创建Web服务器) onRefresh(); // 10. 注册监听器 registerListeners(); // 11. 完成Bean初始化(重点!实例化所有非懒加载单例Bean) finishBeanFactoryInitialization(beanFactory); // 12. 完成刷新(发布ContextRefreshedEvent) finishRefresh(); } } ``` --- ### 六、自动配置原理 #### 1. @SpringBootApplication注解 ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Configuration @EnableAutoConfiguration // 核心注解 @ComponentScan public @interface SpringBootApplication { // ... } ``` #### 2. 自动配置触发流程 ```mermaid sequenceDiagram participant A as @EnableAutoConfiguration participant B as AutoConfigurationImportSelector participant C as spring.factories participant D as @Conditional条件判断 A->>B: 选择导入配置 B->>C: 加载META-INF/spring.factories C->>B: 返回自动配置类列表 B->>D: 执行条件注解过滤 D->>B: 返回有效配置类 B->>Spring: 注册有效配置类 ``` --- ### 七、内嵌Web服务器启动 #### 1. Web服务器创建流程 ```java // ServletWebServerApplicationContext protected void onRefresh() { super.onRefresh(); try { createWebServer(); // 创建Web服务器 } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } private void createWebServer() { // 从BeanFactory获取ServletWebServerFactory ServletWebServerFactory factory = getWebServerFactory(); // 创建WebServer(Tomcat/Jetty/Undertow) this.webServer = factory.getWebServer(getSelfInitializer()); } ``` --- ### 八、启动后扩展点 #### 1. 常用扩展接口 | 接口类型 | 执行时机 | 典型用途 | |---------------------------|----------------------------|--------------------------| | ApplicationRunner | 上下文刷新完成后 | 启动执行初始化任务 | | CommandLineRunner | 同ApplicationRunner | 命令行参数处理 | | ApplicationListener | 监听特定事件 | 监控启动过程 | | BeanPostProcessor | Bean初始化前后 | Bean增强处理 | ```java // 示例:ApplicationRunner实现 @Component public class MyRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) { System.out.println("应用启动完成!"); } } ``` --- ### 九、性能优化建议 1. **减少组件扫描范围**: ```properties spring.component-scan.base-packages=com.example.core ``` 2. **延迟初始化**: ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication app = new SpringApplication(Application.class); app.setLazyInitialization(true); // 启用全局延迟初始化 app.run(args); } } ``` 3. **排除不必要的自动配置**: ```java @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, CacheAutoConfiguration.class }) ``` --- ### 十、调试技巧 在启动时添加JVM参数: ```bash -Dlogging.level.org.springframework=DEBUG -Ddebug # 打印自动配置报告 ``` 通过这个完整的启动流程分析,可以清晰看到Spring Boot如何通过约定大于配置的原则,将复杂的Spring生态整合为简单易用的启动过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值