乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Boot 概述

    • 章节导读:Spring Boot概述与核心理念
    • Spring Boot是什么
    • Spring Boot与Spring Framework的关系
    • 约定优于配置
  • 第2章 快速入门与第一个应用

    • 章节导读:快速入门与第一个应用
    • SpringApplication
    • 第一个Spring Boot应用
  • 第3章 起步依赖与版本管理

    • 章节导读:起步依赖与版本管理
    • 起步依赖
    • BOM版本管理
  • 第4章 自动配置原理

    • 章节导读:自动配置原理
    • 自动配置原理
    • AutoConfigurationImportSelector
    • 自动配置报告
  • 第5章 核心注解

    • 章节导读:核心注解
    • @SpringBootApplication
    • @EnableAutoConfiguration
    • @ConditionalOnClass
    • @ConditionalOnMissingBean
    • @ConditionalOnBean
    • @ConditionalOnProperty
  • 第6章 外部化配置与属性绑定

    • 章节导读:外部化配置与属性绑定
    • 外部化配置
    • @ConfigurationProperties
    • @Value
    • 配置属性优先级
  • 第7章 Profile 与环境切换

    • 章节导读:Profile与环境切换
    • @Profile
    • 多环境配置文件
  • 第8章 内嵌服务器与部署

    • 章节导读:内嵌服务器与部署
    • 内嵌服务器
    • Fat Jar
  • 第9章 Actuator 与监控

    • 章节导读:Actuator与监控
    • Actuator Health
    • Actuator Info
    • Actuator Metrics
    • 自定义Endpoint
    • 自定义HealthIndicator
  • 第10章 开发工具与最佳实践

    • 章节导读:开发工具与最佳实践
    • Banner自定义
    • 热部署

一句话定位:SpringApplication 是 Spring Boot 应用的启动引擎,run() 方法 orchestrates 整个应用生命周期——从推断应用类型、加载环境、创建容器到刷新上下文,最终将一个普通的 main() 方法转化为一个可对外提供服务的 Spring 应用。


定义与作用

SpringApplication 是 org.springframework.boot 包下的核心类,负责引导(bootstrap)整个 Spring Boot 应用的启动过程。它的 run() 方法不是简单的 "new 一个容器",而是一个经过精心编排的多阶段启动流水线。

它解决的痛点:

传统 Spring Framework 启动SpringApplication.run() 的改进
手动创建 ApplicationContext 子类(如 AnnotationConfigApplicationContext)自动推断应用类型(Servlet / Reactive / None),创建最合适的上下文实现
手动加载 applicationContext.xml 或扫描 @Configuration 类自动推断主类,自动加载组件扫描和自动配置
手动注册 ServletContextListener 和 DispatcherServlet自动创建并配置内嵌 Web 服务器,注册 DispatcherServlet
手动处理命令行参数和配置文件统一准备 Environment,合并命令行参数、系统属性、环境变量、application.yml
无统一的启动生命周期扩展点提供 ApplicationContextInitializer、ApplicationListener、ApplicationRunner 等标准扩展点

适用位置与常用属性

SpringApplication 是一个类,不是注解。它在项目中唯一出现的位置是主启动类的 main() 方法。

适用位置

package com.feixiang.student;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementApplication {
    public static void main(String[] args) {
        // 唯一、标准的调用方式
        SpringApplication.run(StudentManagementApplication.class, args);
    }
}

进阶用法:自定义 SpringApplication 实例

package com.feixiang.student;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementApplication {
    public static void main(String[] args) {
        // 操作前:标准写法,无法定制启动行为
        // SpringApplication.run(StudentManagementApplication.class, args);

        // 操作后:自定义 SpringApplication,控制 Banner、懒初始化等
        SpringApplication application = new SpringApplication(StudentManagementApplication.class);
        application.setBannerMode(Banner.Mode.OFF);          // 关闭 Banner
        application.setLazyInitialization(true);             // 开启全局懒初始化
        application.setAdditionalProfiles("dev");            // 预设激活的 Profile
        application.run(args);
    }
}
属性/方法说明典型场景
setBannerMode(Banner.Mode.OFF)关闭启动 BannerCI/CD 环境减少日志噪音
setLazyInitialization(true)全局懒初始化 Bean开发环境加速启动,非所有 Bean 立即创建
setAdditionalProfiles(String...)预设激活的 Profile测试环境强制激活 dev Profile
setDefaultProperties(Properties)设置默认属性为未配置项提供应用级默认值
setApplicationContextClass(Class)强制指定上下文类型极罕见,通常由自动推断处理
addListeners(ApplicationListener...)添加应用监听器注册自定义的 ApplicationReadyEvent 监听器
addInitializers(ApplicationContextInitializer...)添加上下文初始化器在容器刷新前修改 ConfigurableEnvironment

核心原理

SpringApplication.run() 的 12 阶段启动流程

阶段详解:

阶段核心动作对开发者的意义
1. 创建实例调用 new SpringApplication(primarySources)可在此之前通过构造方法定制属性
2. 推断应用类型检测 classpath 是否存在 spring-webmvc、spring-webflux决定创建哪种 ApplicationContext 实现
3-4. 加载扩展从 spring.factories 读取 ApplicationContextInitializer 和 ApplicationListener框架级扩展点,开发者可自定义
5. 推断主类通过堆栈反推哪个类调用了 run()决定 @ComponentScan 的默认根包
6. 准备环境加载 application.yml、命令行参数、系统属性、环境变量外部化配置在此阶段合并到 Environment
7. 打印 Banner输出 :: Spring Boot :: (v2.7.18)可自定义 banner.txt 或关闭
8-9. 创建与预处理实例化 ApplicationContext,执行初始化器初始化器可在 Bean 加载前修改上下文状态
10. 刷新上下文调用 refresh(),执行自动配置、条件评估、Bean 实例化最核心的阶段,自动配置在此发生
11-12. 后处理与 Runner调用 afterRefresh,执行 ApplicationRunner启动后初始化逻辑(如预加载缓存)放在此处
13. 发布就绪事件ApplicationReadyEvent监听器可在此执行启动后的异步初始化

启动流程时序图(简化版)


完整示例

场景说明

飞翔科技的学生成绩管理系统启动后,架构师白歌希望小崔在控制台输出一条自定义的启动完成消息,并记录启动耗时。小崔通过自定义 SpringApplication 和 ApplicationRunner 来实现。

操作前:标准启动方式(无法定制)

package com.feixiang.student;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementApplication {
    public static void main(String[] args) {
        // 标准写法:无法关闭 Banner、无法预设 Profile、无法自定义启动后逻辑
        SpringApplication.run(StudentManagementApplication.class, args);
    }
}

启动日志(默认 Banner 输出):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.7.18)

... 正常启动日志 ...

小崔的需求:关闭 Banner 减少 CI 日志噪音;启动后输出 "学生成绩管理系统已就绪";记录启动耗时。

使用自定义 SpringApplication 的完整代码

主启动类:

package com.feixiang.student;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudentManagementApplication {

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        SpringApplication application = new SpringApplication(StudentManagementApplication.class);
        application.setBannerMode(Banner.Mode.OFF);   // 关闭 Banner
        application.setAdditionalProfiles("dev");     // 预设 dev 环境
        application.run(args);

        long cost = System.currentTimeMillis() - startTime;
        System.out.println("[飞翔科技] 学生成绩管理系统启动完成,耗时:" + cost + "ms");
    }
}

启动后逻辑:使用 ApplicationRunner(替代 main 中手动计时,更标准):

package com.feixiang.student.config;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class StartupReportRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("========================================");
        System.out.println("  广州飞翔科技 - 学生成绩管理系统");
        System.out.println("  系统状态:已就绪");
        System.out.println("  监听端口:8080");
        System.out.println("  数据库  :student_db");
        System.out.println("========================================");
    }
}

启动日志(关闭 Banner 后):

2024-05-20 09:25:10.123  INFO 12345 --- [main] c.f.s.StudentManagementApplication : Starting StudentManagementApplication using Java 1.8
2024-05-20 09:25:10.456  INFO 12345 --- [main] c.f.s.StudentManagementApplication : The following profiles are active: dev
2024-05-20 09:25:11.789  INFO 12345 --- [main] o.s.b.w.e.t.TomcatWebServer          : Tomcat started on port(s): 8080 (http)
2024-05-20 09:25:11.890  INFO 12345 --- [main] c.f.s.StudentManagementApplication : Started in 1.876 seconds
========================================
  广州飞翔科技 - 学生成绩管理系统
  系统状态:已就绪
  监听端口:8080
  数据库  :student_db
========================================

操作后运行结果及分析

分析:

  1. Banner 关闭:setBannerMode(Banner.Mode.OFF) 生效,Spring Boot 的 ASCII 艺术 Banner 不再输出,CI 日志更干净。
  2. Profile 预设:setAdditionalProfiles("dev") 使 application-dev.yml 的配置被激活,开发环境数据库连接自动生效。
  3. 启动后逻辑:ApplicationRunner 的 run() 方法在容器刷新完成后、应用就绪前执行,适合执行预加载缓存、初始化定时任务等启动后初始化操作。它与 CommandLineRunner 的区别在于参数封装——ApplicationRunner 接收封装后的 ApplicationArguments,而 CommandLineRunner 接收原始 String[]。
  4. 启动事件:ApplicationReadyEvent 在 ApplicationRunner 执行后发布,如果需要在启动完成后执行异步操作(如发送启动通知到钉钉),应监听此事件。

易错场景与面试考点

易错场景:在 main() 中写业务逻辑,期望容器已就绪

小崔曾犯过这样的错误:他想在 main() 中直接调用 Service 方法查询学生成绩。

错误示范:

package com.feixiang.student;

import com.feixiang.student.service.StudentService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class StudentManagementApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
            SpringApplication.run(StudentManagementApplication.class, args);

        // 错误:虽然此时容器已创建,但启动流程尚未完成
        // 如果此时调用 Bean 执行复杂操作,可能遇到未完全初始化的组件
        StudentService service = context.getBean(StudentService.class);
        System.out.println(service.queryScore("2024001"));
        
        // 更严重:获取 Bean 后直接关闭上下文,Runner 和 ReadyEvent 都没执行
        context.close();
    }
}

后果:

  • 虽然 SpringApplication.run() 返回的 ApplicationContext 已经刷新,但 ApplicationRunner 和 ApplicationReadyEvent 尚未执行。如果业务逻辑依赖这些启动后初始化(如缓存预热),会直接失败。
  • 手动 context.close() 导致应用启动后立即关闭,Tomcat 尚未接受请求就终止。

正确做法:所有启动后业务初始化逻辑应放在 ApplicationRunner 或 ApplicationReadyEvent 监听器中,而非 main() 中直接写业务代码。

面试考点

Q:SpringApplication.run() 内部做了哪些关键步骤?

① 创建 SpringApplication 实例;② 推断应用类型(Servlet / Reactive / None);③ 加载 ApplicationContextInitializer 和 ApplicationListener;④ 推断主类;⑤ 准备 Environment(合并命令行参数、系统属性、环境变量、application.yml);⑥ 打印 Banner;⑦ 创建 ApplicationContext;⑧ 预处理上下文;⑨ 刷新上下文(核心:自动配置 + Bean 加载);⑩ 后处理;⑪ 调用 ApplicationRunner / CommandLineRunner;⑫ 发布 ApplicationReadyEvent。

Q:ApplicationRunner 和 CommandLineRunner 有什么区别?

两者都在容器刷新完成后、应用就绪前执行。区别:ApplicationRunner 接收 ApplicationArguments(封装后的参数,支持 --key=value 和 key=value 解析);CommandLineRunner 接收原始 String[] args。如果项目中同时存在两者,可通过 @Order 或实现 Ordered 接口控制执行顺序。

Q:如何关闭 Spring Boot 的 Banner?

方式一:SpringApplication.setBannerMode(Banner.Mode.OFF);方式二:application.yml 中配置 spring.main.banner-mode=off;方式三:在 resources 目录下放置自定义 banner.txt 替换默认图案(支持 ${spring-boot.version} 等占位符)。

Q:Spring Boot 推断应用类型的依据是什么?

检测 classpath 中是否存在 spring-webmvc(存在则为 SERVLET)、spring-webflux(存在则为 REACTIVE)。如果两者都存在,SERVLET 优先。如果两者都不存在,则为 NONE(非 Web 应用,如批处理或命令行工具)。

上一页
章节导读:快速入门与第一个应用
下一页
第一个Spring Boot应用