Skip to content

Spring Boot 启动与配置 ​

学习目标 ​

  • 能说明 @SpringBootApplication 做了什么,以及启动类所在包对组件扫描的影响。
  • 能用 application.yml、环境变量和 @ConfigurationProperties 管理外部配置。
  • 能判断条件装配为什么生效或跳过,并用日志和条件报告定位启动失败。

核心知识点 ​

专业术语 ​

  • 启动入口(bootstrap entry point):调用 SpringApplication.run 创建并刷新 ApplicationContext 的类。
  • 组件扫描(component scan):根据包路径发现带 @Component、@Service 等注解的 Bean 定义。
  • 外部化配置(externalized configuration):把环境差异放到配置文件、环境变量或命令行参数,代码只读取绑定结果。
  • 条件装配(conditional configuration):只有满足类、Bean、属性或资源条件时,自动配置才注册相关 Bean。
  • 配置属性(configuration properties):以带前缀的结构把多项配置绑定到类型安全对象。

白话解释与边界 ​

Spring Boot 启动像组装一座工厂:先准备环境和属性,再扫描组件、注册自动配置,最后创建 Bean 并刷新容器。@SpringBootApplication 不是“把所有类都加载”,而是组合了配置类、组件扫描和自动配置入口;启动类放在业务包的共同父包通常最省心。

配置文件只是输入,不是安全边界。密钥、密码等敏感值应由受控的环境变量或密钥服务注入,并限制日志输出。配置绑定成功也不代表业务值合理,端口、超时和列表长度仍应在应用层校验。自动配置节省样板代码,但显式 Bean 和条件顺序发生冲突时,要查看报告而不是盲目增加注解。

版本与兼容基线 ​

本文按 JDK 20 的写法组织示例,使用的 var、record 等语法在 Java 17 已可用,不依赖 JDK 20 之后才出现的 API。项目兼容基线是 Java 17、Spring Boot 4.1.0 与 Spring Framework 7。Spring Boot 4 使用 Jakarta EE 命名空间,示例使用 jakarta.*,它替代旧版 javax.*;迁移旧项目时要以实际依赖版本为准。

常用用法 ​

@SpringBootApplication:声明启动入口 ​

用途:用于声明应用配置、组件扫描和自动配置的根入口;启动类应放在业务包的共同父包。

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

@SpringBootApplication
// 作用:@SpringBootApplication;声明启动入口。
public class BackendApplication {
    public static void main(String[] args) {
        var context = SpringApplication.run(BackendApplication.class, args);
// 作用:var context = SpringApplication.run(BackendApplication.class, args);;声明启动入口。
// 初始状态:context = SpringApplication.run(BackendApplication.class, args)。
        System.out.println(context != null);
// 输出:true
    }
}
// 说明:SpringApplication.run 以 BackendApplication 为主配置源创建 ApplicationContext;打印 true。@SpringBootApplication 让容器从该类所在包向下扫描并导入自动配置,直接调用 main 之外的普通 new 不会产生这些容器行为。

如果启动类放在过深的子包,扫描范围可能漏掉配置或控制器;也可以用 scanBasePackages 明确范围,但要同步考虑第三方自动配置和测试包。

@Configuration:声明配置类 ​

用途:用于把一组 Bean 定义交给 Spring 容器;下例注册名为 systemClock 的 UTC Clock,业务 Bean 可通过构造器按类型注入。

java
// 作用:通过 @Configuration 声明配置类。
import java.time.Clock;
import java.time.ZoneOffset;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
// 作用:@Configuration;声明配置类。
class TimeConfiguration {
    @Bean("systemClock")
    Clock systemClock() {
        return Clock.system(ZoneOffset.UTC);
// 作用:return Clock.system(ZoneOffset.UTC);;声明配置类。
// 初始状态:return Clock.system(ZoneOffset.UTC)。
    }
}
// 结果:容器中存在名为 systemClock、时区为 Z 的 Clock Bean

只有 Spring 扫描或显式导入 TimeConfiguration 时,@Configuration 与 @Bean 才会参与容器注册;直接 new TimeConfiguration().systemClock() 只是普通 Java 方法调用,不会建立 Bean 生命周期或依赖注入关系。

application.yml:声明外部配置 ​

用途:用于按环境层级表达端口、超时和业务开关;文件中的值最终会进入 Spring Environment。

java
String yml = "server:\n  port: 8080\nclient:\n  timeout: 2s\n";
// 初始状态:yml = "server:\n port: 8080\nclient:\n timeout: 2s\n"。
// 作用:String yml = "server:\n port: 8080\nclient:\n timeout: 2s\n";;声明外部配置。
boolean hasPort = yml.contains("port: 8080");
// 作用:boolean hasPort = yml.contains("port: 8080");;声明外部配置。
// 初始状态:hasPort = yml.contains("port: 8080")。
System.out.println(hasPort);
// 输出:true
// 说明:这段 application.yml 文本声明 server.port=8080、client.timeout=2s;这里只用 contains 验证文本,未启动 Spring,因此没有加载 Environment 或绑定配置。

实际项目把上面的内容保存为 application.yml,再用 profile 文件或环境变量覆盖差异项。YAML 缩进属于语法,键名统一使用小写短横线可减少绑定歧义;不要把令牌直接提交到仓库。

@ConfigurationProperties:实现类型安全绑定 ​

用途:用于把同一前缀下的多项配置绑定到不可变或可校验的类型,避免在业务代码散落字符串键。

java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "client")
// 初始状态:prefix = "client")。
// 作用:@ConfigurationProperties(prefix = "client");实现类型安全绑定。
public record ClientProperties(String baseUrl, int timeoutSeconds) {
    public ClientProperties {
        if (timeoutSeconds <= 0) {
            throw new IllegalArgumentException("timeoutSeconds must be positive");
// 初始状态:throw new IllegalArgumentException("timeoutSeconds must be positive")。
// 作用:throw new IllegalArgumentException("timeoutSeconds must be positive");;实现类型安全绑定。
        }
    }
}

System.out.println(new ClientProperties("https://api.example.test", 2).timeoutSeconds());
// 输出:2
// 说明:直接 new 得到 baseUrl=https://api.example.test、timeoutSeconds=2 并打印 2;只有容器启用配置属性扫描后,才会把 client.* 外部属性绑定为 ClientProperties Bean。

要让类型进入容器,还需使用 @ConfigurationPropertiesScan 或 @EnableConfigurationProperties(ClientProperties.class)。绑定解决类型转换,不会自动替代业务校验;缺失值、单位和默认值必须在契约中写清楚。

不常用但需要知道 ​

条件装配:按环境选择 Bean ​

用途:用于让可选组件只在类、属性或 Bean 条件满足时创建,避免开发环境和生产环境硬编码两套启动逻辑。

java
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class MetricsConfiguration {
    @Bean
    @ConditionalOnProperty(name = "metrics.enabled", havingValue = "true")
    String metricsMarker() {
        return "metrics-on";
    }
}

System.out.println("metrics.enabled=true");
// 输出:metrics.enabled=true
// 作用:用于让可选组件只在类、属性或 Bean 条件满足时创建,避免开发环境和生产环境硬编码两套启动逻辑。

@ConditionalOnMissingBean 常用于给使用方留下替换默认 Bean 的空间;条件没有满足时不是异常,而是该配置被跳过。属性条件的键名和默认行为要写进部署说明。

启动失败定位:先看根因与条件报告 ​

用途:用于把“启动失败”拆成配置解析、Bean 创建、端口占用和条件不匹配等可验证原因。

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.diagnostics.FailureAnalysis;

FailureAnalysis analysis = new FailureAnalysis(
    "端口已被占用", "server.port", new IllegalStateException("bind failed"));
// 初始状态:analysis = new FailureAnalysis(。
// 作用:FailureAnalysis analysis = new FailureAnalysis(;先看根因与条件报告。
// 作用:"端口已被占用", "server.port", new IllegalStateException("bind failed"));;先看根因与条件报告。
System.out.println(analysis.getDescription());
// 输出:端口已被占用
// 作用:用于把“启动失败”拆成配置解析、Bean 创建、端口占用和条件不匹配等可验证原因。

先读最内层 Caused by,再确认 profile、环境变量和端口;需要自动配置原因时使用 --debug 或开启条件评估报告。不要只复制最后一行异常,也不要在未确认根因时增加全局排除项。

ApplicationRunner:容器刷新后执行一次 ​

用途:用于在应用上下文完成刷新后执行轻量初始化;不适合阻塞启动线程或处理可重试的长任务。

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

@Component
class WarmupRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        System.out.println("cache warmup");
// 输出:cache warmup
    }
}
// 说明:组件扫描注册 warmupRunner Bean,Spring Boot 在 ApplicationContext 刷新后调用 run,并打印 cache warmup;直接 new WarmupRunner().run(...) 只是普通调用,不验证 Runner 启动时序。

多个 Runner 可用 @Order 排序;如果初始化失败,默认会阻止应用正常启动。耗时任务应转成可观测的异步作业,并明确失败是否允许服务继续提供流量。

Environment:读取最终属性视图 ​

用途:用于诊断属性来源或读取少量动态开关;重复读取同一业务配置时优先使用 @ConfigurationProperties。

java
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
class PropertyProbe {
    PropertyProbe(Environment environment) {
        String port = environment.getProperty("server.port", "8080");
// 初始状态:port = environment.getProperty("server.port", "8080")。
// 作用:String port = environment.getProperty("server.port", "8080");;读取最终属性视图。
        System.out.println(port);
// 输出:8080
    }
}
// 说明:Spring 注入最终 Environment,读取键 server.port;键缺失时使用默认字符串 8080。PropertyProbe 必须由容器创建,直接 new 需要手工传入 Environment,不验证属性源合并。

Environment 会合并多个 property source,具体优先级由 Spring Boot 配置规则决定。读取敏感属性后不要打印原值;如果属性必须有范围约束,仍应在绑定类型或启动检查中验证。

继续阅读 ​

  • List 基础:需要处理配置列表时先查 List 的可变性和视图边界。
  • Map 基础:需要读取键值配置或构建索引时查 Map 的合并语义。
  • String 文本处理:需要规范化环境变量、URL 或 profile 名称时查字符串边界。
  • JDBC 与事务:应用启动后连接数据库时,区分连接池初始化和事务边界。

简单案例 ​

java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@ConfigurationProperties(prefix = "feature")
record FeatureProperties(boolean enabled, int retryLimit) {
    FeatureProperties {
        if (retryLimit < 0 || retryLimit > 10) {
            throw new IllegalArgumentException("retryLimit must be 0..10");
// 初始状态:throw new IllegalArgumentException("retryLimit must be 0..10")。
// 作用:throw new IllegalArgumentException("retryLimit must be 0..10");;读取最终属性视图。
        }
    }
}

@Configuration
@EnableConfigurationProperties(FeatureProperties.class)
class FeatureConfiguration {
    FeatureConfiguration(FeatureProperties properties) {
        System.out.println(properties.enabled() + "/" + properties.retryLimit());
// 输出:true/3
    }
}

框架片段需容器运行:application.yml 可以提供 feature.enabled: true 和 feature.retry-limit: 3,绑定器负责把短横线键映射到 record 组件。生产部署应把 profile、环境变量和默认值写成可审计的配置清单;启动校验失败时让服务保持不可用,避免用错误默认值继续运行。

易混点 ​

  • @SpringBootApplication 是组合注解,不等于扫描整个磁盘;包位置决定默认扫描范围。
  • application.yml 的键值只是输入,@ConfigurationProperties 绑定成功后仍要校验业务范围。
  • Environment 适合少量读取和诊断,重复的业务配置应收敛到类型安全的属性类。
  • 条件装配未生效通常是条件不满足,不一定是 Spring “漏注册”;先打开条件报告。
  • ApplicationRunner 运行在启动流程后但仍占用启动时序,长耗时初始化要有超时、失败和可观测性设计。

课后小问 ​

  1. 为什么启动类通常放在业务包的共同父包? 答案:因为 @SpringBootApplication 默认从启动类所在包向下组件扫描。 解析:放在过深子包会漏掉同级或上级包中的控制器、服务和配置;需要跨包时应显式配置扫描范围并测试启动结果。

  2. @ConfigurationProperties 能否代替所有配置校验? 答案:不能,它负责把外部值转换并绑定到对象,业务范围和跨字段约束仍需额外校验。 解析:例如超时必须为正、重试次数必须在上限内,这些规则应在构造器、验证器或启动检查中明确表达。

  3. 条件装配没有创建 Bean 时应该先查什么? 答案:先查属性、类路径、候选 Bean 和 profile 是否满足条件,再看条件评估报告。 解析:条件配置被跳过可能是预期行为;直接排除自动配置会掩盖环境或命名错误。

本节小结 ​

  • @SpringBootApplication 连接启动、组件扫描和自动配置,包边界决定默认扫描范围。
  • application.yml、环境变量和命令行参数提供外部化配置,敏感值不应写入仓库或日志。
  • @ConfigurationProperties 把一组配置收敛成类型,绑定之后仍要做范围与跨字段校验。
  • 条件装配和 Runner 改变启动时序,遇到失败应结合根因堆栈与条件报告定位。

快速回顾 ​

  • 能写出 SpringApplication.run 与 @SpringBootApplication 的最小入口。
  • 能说明配置文件、环境变量与 Environment 的读取边界。
  • 能用 @ConfigurationProperties 绑定带前缀的类型并设置默认值或约束。
  • 能区分启动失败根因、条件未满足和启动后初始化失败。