Skip to content

第三部分:Spring Boot 与 Spring Cloud 高频选择题 40 道(含答案解析) ​

一、Spring Boot 启动、自动配置与配置文件(S01–S08) ​

S01 · 知识点:@SpringBootApplication 组合注解 ​

【题目】按照 Spring Boot 官方定义,@SpringBootApplication 注解是由以下哪三个注解组合而成的? A. @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan B. @Configuration + @EnableAutoConfiguration + @ComponentScan C. @SpringBootConfiguration + @EnableCaching + @Import D. @Configuration + @EnableAutoConfiguration + @SpringBootTest

答案:A

【考点】@SpringBootApplication 组合注解的构成及其与 @Configuration 的关系。

【结论】选 A。@SpringBootApplication 由 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan 三个元注解组合而成。

【逐项辨析】

  • A. 正确。 查看源码可知,@SpringBootApplication 上直接标注了这三个注解,属于官方精确声明的组合。
  • B. 错误。 虽然 @SpringBootConfiguration 本质就是 @Configuration 的派生注解(元注解包含 @Configuration),功能层面两者等价,但严格按官方源码定义,组合成员是 @SpringBootConfiguration 而非 @Configuration。
  • C. 错误。 @EnableCaching 用于开启缓存功能,@Import 用于导入配置类,二者均不属于 @SpringBootApplication 的组成部分。
  • D. 错误。 @SpringBootTest 是测试模块的注解,与主类启动注解无关。

【知识点】 @SpringBootApplication 是一个便捷的“三合一大礼包”,其内部通过 @AliasFor 完成属性别名映射。

  1. @SpringBootConfiguration:继承自 @Configuration,标记该类为配置类,同时被 Spring Boot 专属工具链识别(如配置属性绑定时的特殊处理)。
  2. @EnableAutoConfiguration:通过 @Import(AutoConfigurationImportSelector.class) 触发自动配置。启动时读取 classpath 下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 2.7+)或 META-INF/spring.factories(旧版),加载候选自动配置类,再经 @Conditional 家族条件过滤,最终决定生效的 Bean。
  3. @ComponentScan:默认扫描当前启动类所在包及其子包下的 @Component、@Service、@Repository、@Controller 等 Spring 组件。可通过 basePackages 或 basePackageClasses 显式调整扫描路径。

值得注意的是,@SpringBootApplication 的 exclude 和 excludeName 属性实际是通过 @AliasFor 透传给 @EnableAutoConfiguration 的,用于排除特定自动配置类。

【记忆锚点】 “SpringBoot 靠 ComponentScan 找人,靠 EnableAuto 干活,自身是 Configuration。”——首字母 SCE(或记“三 C 一 E”中的核心 trio)。

【易混对比】

对比维度@Configuration@SpringBootConfiguration
来源Spring Framework 原生Spring Boot 专属
元注解关系基础注解元注解包含 @Configuration
识别范围所有 Spring 上下文Spring Boot 特定工具链额外识别
能否互换功能层面可互换官方组合声明中不可替代
对比维度@ComponentScan@EnableAutoConfiguration
职责发现并注册用户自定义组件加载第三方 starters 的预设配置
扫描目标用户代码中的注解类classpath 下的 imports/factories 文件
可控方式basePackages / filterexclude / @Conditional

换问法: 若题目问“从功能等价角度,@SpringBootApplication 可以用哪三个注解替代?”,则答案变为 B(@Configuration + @EnableAutoConfiguration + @ComponentScan)。

【自测】

某项目启动类所在包为 com.example.app,同时在 com.example.lib 下有一个 @Service 类。若启动类仅标注 @SpringBootApplication 且未修改任何属性,该 Service 能否被扫描到?

答:不能。因为 @ComponentScan 默认只扫描启动类所在包(com.example.app)及其子包,com.example.lib 不在此路径下。如需扫描,应显式设置 basePackages = {"com.example.app", "com.example.lib"} 或使用 @ComponentScan 单独配置。与第 S02 题连考。

【知识关联】

  • 同库关联:与 S02(自动配置)、S03(条件装配)、S08(启动流程)Boot 核心题群。
  • 实现层:组合 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan;主类所在包向下扫描。
  • 面试追问:① 扫描不到 Bean 的原因?② 如何排除自动配置?

【拓展延伸】

  • 变式问法:问三个元注解分别是什么;@ComponentScan 自定义。
  • 版本差异:Spring Boot 2.x/3.x 注解稳定;3.x 基于 Spring Framework 6 / Jakarta。
  • 工程注意点:主类放根包;多模块注意扫描路径;不要滥用 scanBasePackages 忘记业务包。

S02 · 知识点:自动配置原理 ​

【题目】Spring Boot 自动配置的核心机制,是由以下哪个注解触发开启的?

A. @ComponentScan B. @EnableAutoConfiguration C. @PropertySource D. @ConfigurationProperties

答案:B

【考点】自动配置的入口注解 @EnableAutoConfiguration 及 AutoConfigurationImportSelector 的工作机制。

【结论】选 B。@EnableAutoConfiguration 是 Spring Boot 自动配置的入口注解,负责触发整个自动配置流程。

【逐项辨析】

  • A. 错误。 @ComponentScan 仅负责组件扫描与注册,不参与第三方 starter 的预设配置加载。
  • B. 正确。 @EnableAutoConfiguration 通过 @Import 导入 AutoConfigurationImportSelector,在容器刷新阶段读取 imports 文件并加载候选自动配置类。
  • C. 错误。 @PropertySource 用于将指定的属性文件加载到 Environment 中,与自动配置类加载无关。
  • D. 错误。 @ConfigurationProperties 用于将外部配置属性绑定到 POJO,属于配置消费端,而非配置触发端。

【知识点】 自动配置的完整链路可分为四个阶段:

  1. 触发阶段:@EnableAutoConfiguration 被解析,Spring Boot 调用 AutoConfigurationImportSelector#selectImports。
  2. 读取阶段:.imports 文件由 ImportCandidates.load(...)(内部用 ClassLoader.getResources 读 META-INF/spring/*.imports)读取,不是 SpringFactoriesLoader——后者只服务 spring.factories(实测 3.4.6 的 spring.factories 已无 EnableAutoConfiguration 键,自动配置清单在 imports 文件里,共 153 行)。 文件(Spring Boot 3.x / 2.7+),逐行获取全限定类名。
  3. 过滤阶段:利用 Spring Boot 的 @Conditional 家族(@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等)对候选类进行条件评估。不满足条件的配置类被剔除。
  4. 生效阶段:剩余配置类被注册到 BeanDefinitionMap,随后由容器实例化。若存在 @AutoConfigureBefore / @AutoConfigureAfter,还会按顺序调整配置类优先级。

Spring Boot 2.7 开始推荐使用 imports 文件替代 spring.factories 中的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键,以获得更快的启动性能和更清晰的结构。

【记忆锚点】 “Enable 是开关,Selector 去挑,imports 列清单,Conditional 把门看。”

【易混对比】

对比维度@EnableAutoConfiguration@ComponentScan
作用时机容器 refresh 时的配置导入阶段容器启动时的类路径扫描阶段
数据来源META-INF/spring/*.imports用户代码中的注解类
典型产物DataSourceAutoConfiguration 等用户写的 @Service、@Controller
排除方式@SpringBootApplication(exclude=...)@ComponentScan(excludeFilters=...)
对比维度@PropertySource@ConfigurationProperties
作用加载外部 .properties 文件到 Environment将 Environment 中的属性绑定到 Bean
使用位置配置类上POJO 类上或 @Bean 方法参数
是否支持松散绑定不涉及支持 kebab/snake/camel 三种风格

换问法: 若题目问“Spring Boot 从哪个文件中读取候选自动配置类的全限定名?”,则应选“META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports”。

【自测】

自定义一个 starter,需在 resources/META-INF/spring/ 下新建什么文件才能让 Spring Boot 自动加载其中的配置类?该配置类又应标注什么注解才能被识别为自动配置类?

答:需新建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,每行列出一个自动配置类的全限定名;配置类本身应标注 @AutoConfiguration(Spring Boot 2.7+)或 @Configuration + 各类 @Conditional 条件注解。与第 S03 题连考。

【知识关联】

  • 同库关联:与 S01/S03;starter 机制与 spring.factories / AutoConfiguration.imports。
  • 实现层:EnableAutoConfiguration → 加载自动配置类 → @Conditional 过滤 → 注册匹配的 Bean。
  • 面试追问:① 自动配置如何被触发?② 如何写自定义 starter?

【拓展延伸】

  • 变式问法:@ConditionalOnClass 作用;Debug 自动配置报告。
  • 版本差异:Boot 2.7+ 推荐 AutoConfiguration.imports 替代 spring.factories 注册自动配置。
  • 工程注意点:用 --debug / actuator conditions 排查;自定义 starter 注意依赖与条件。

S03 · 知识点:条件装配注解 ​

【题目】以下哪个注解可以根据配置文件(如 application.yml)中的属性值是否存在或匹配来决定是否装配 Bean?

A. @ConditionalOnClass B. @ConditionalOnMissingBean C. @ConditionalOnProperty D. @ConditionalOnBean

答案:C

【考点】@Conditional 系列条件注解的职责区分。

【结论】选 C。@ConditionalOnProperty 专用于根据外部配置文件中的属性值是否匹配来决定是否装配 Bean。

【逐项辨析】

  • A. 错误。 @ConditionalOnClass 判断的是 classpath 下是否存在指定类,与配置文件无关。
  • B. 错误。 @ConditionalOnMissingBean 判断的是 Spring 容器中是否不存在指定类型的 Bean,常用于提供默认实现。
  • C. 正确。 @ConditionalOnProperty 通过 prefix + name(或 value)指定属性名,通过 havingValue 指定期望值,通过 matchIfMissing 控制属性缺失时的默认行为,实现基于配置开关的功能控制。
  • D. 错误。 @ConditionalOnBean 判断的是容器中是否已存在指定 Bean,与配置文件无关。

【知识点】 @ConditionalOnProperty 的核心属性包括:

  • prefix:属性前缀,如 app.feature。
  • name / value:具体属性名数组,如 "enabled"。
  • havingValue:期望的属性值,只有实际值与之相等时条件才成立。
  • matchIfMissing:当属性不存在时,默认为 false(不装配);若设为 true,则属性缺失时也满足条件。

典型应用场景:

yaml
app:
  feature:
    enabled: true
java
@ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true")
@Bean
public FeatureService featureService() { ... }

此外,Spring Boot 还提供了 @ConditionalOnResource(判断资源文件是否存在)、@ConditionalOnWebApplication / @ConditionalOnNotWebApplication(判断应用类型)等扩展条件注解,共同构成“条件装配”体系,实现“按需加载”。

【记忆锚点】 “Property 读配置,Class 看类库,Bean 看容器,Missing 看缺位。”

【易混对比】

注解判断依据典型用途
@ConditionalOnClassclasspath 中是否存在某类有某依赖时才装配(如存在 Redis 才配 RedisTemplate)
@ConditionalOnMissingBean容器中是否缺少某 Bean提供默认实现,允许用户覆盖
@ConditionalOnBean容器中是否已存在某 Bean依赖其他 Bean 存在时才装配
@ConditionalOnProperty配置属性是否匹配功能开关(如 enabled=true)

换问法: 若题目问“当用户未自定义 RedisTemplate 时,Spring Boot 才自动创建一个默认的 RedisTemplate,这是由哪个注解实现的?”,答案应为 @ConditionalOnMissingBean。

【自测】

某配置类上标注了 @ConditionalOnProperty(prefix = "mq", name = "type", havingValue = "kafka"),而 application.yml 中仅配置了 mq.type=rabbitmq,该配置类中的 Bean 会被实例化吗?若将 matchIfMissing 设为 true 且删除 mq.type 配置,结果又如何?

答:第一种情况不会实例化,因为 havingValue="kafka" 与实际值 "rabbitmq" 不匹配;第二种情况会实例化,因为 matchIfMissing=true 时属性缺失视为条件成立。与第 S04 题连考。

【知识关联】

  • 同库关联:与 S02;@ConditionalOnBean/Class/MissingBean/Property。
  • 实现层:Condition 接口在配置解析阶段求值。
  • 面试追问:① 条件评估时机?② 顺序与覆盖?

【拓展延伸】

  • 变式问法:用户自定义 Bean 如何覆盖自动配置(@ConditionalOnMissingBean)。
  • 版本差异:注解稳定。
  • 工程注意点:条件注解要可预期;避免隐式难排查的开关。

S04 · 知识点:配置文件优先级 ​

【题目】在 Spring Boot 中,同一目录下同时存在 application.properties 与 application.yml,且二者配置了同名属性,最终生效的是?

A. 随机加载其中一个 B. application.yml 中的配置 C. 二者报错,启动失败 D. application.properties 中的配置

答案:D

【考点】配置文件格式的加载顺序与同名属性覆盖规则。

【结论】选 D。当同一目录下同时存在 application.properties 与 application.yml 时,application.properties 的优先级更高,同名属性以其为准。

【逐项辨析】

  • D. 正确。 Spring Boot 官方文档明确规定,同一位置的 application.properties 优先级高于 application.yml(以及 .yaml)。
  • B. 错误。 application.yml 虽然也会被加载,但在同名属性冲突时会被 properties 文件覆盖。
  • C. 错误。 二者不存在冲突报错机制,Spring Boot 会平滑合并所有来源的配置,按优先级规则覆盖同名项。
  • A. 错误。 加载顺序由框架严格定义,不存在随机性。

【知识点】 Spring Boot 的配置是一个“多源合并、优先级覆盖”的体系。官方优先级从高到低(节选)依次为:

  1. 命令行参数(--server.port=9090)
  2. Java 系统属性(System.getProperties())
  3. 操作系统环境变量
  4. jar 包外部的 application-{profile}.properties / .yml
  5. jar 包外部的 application.properties / .yml
  6. jar 包内部的 application-{profile}.properties / .yml
  7. jar 包内部的 application.properties / .yml
  8. @PropertySource 加载的属性
  9. SpringApplication 的默认属性

在同一层级内部,properties 始终优先于 yml。这意味着若 jar 内部同时存在 application.properties 和 application.yml,properties 中同名属性会覆盖 yml。而若 jar 外部存在 application.yml,其优先级仍高于 jar 内部的 application.properties(因为“外部优于内部”的层级规则优先于“格式优先”规则)。

【记忆锚点】 “同层 properties 压 yml,外部配置压内部,命令行是王者。”

【易混对比】

场景生效来源原因
同目录 application.properties vs application.ymlapplication.properties格式优先级:properties > yml
jar 外部 application.yml vs jar 内部 application.propertiesjar 外部 application.yml位置优先级:外部 > 内部
application-prod.yml vs application.ymlapplication-prod.ymlProfile 专属文件覆盖默认文件
命令行参数 vs 环境变量命令行参数命令行参数优先级最高

换问法: 若题目问“将 application.properties 放在 jar 包同级目录下,application.yml 放在 jar 包内 classpath 根目录,同名属性以谁为准?”,答案应为 jar 包外部的 application.properties(外部位置优先)。

【自测】

项目中 jar 包内部同时存在 application.properties(server.port=8080)和 application.yml(server.port: 9090),且 jar 包外部存在 application.yml(server.port: 7070)。启动后实际监听端口是多少?

答:7070。解析:外部 application.yml 优先级高于内部 application.properties(外部位置优先规则 > 格式优先规则)。与第 S05 题连考。

【知识关联】

  • 同库关联:与 S05(绑定)、S06(Profile)、S29(配置中心)。
  • 实现层:Environment 抽象多 PropertySource;命令行 > 系统环境 > application-{profile}.yml > application.yml 等(有文档优先级)。
  • 面试追问:① 外部化配置优先级?② 如何覆盖?

【拓展延伸】

  • 变式问法:--server.port= 与 yml 谁优先。
  • 版本差异:细节以对应版本官方文档为准。
  • 工程注意点:敏感配置走环境变量/配置中心;本地 profile 与生产隔离。

S05 · 知识点:@ConfigurationProperties 宽松绑定 ​

【题目】使用 @ConfigurationProperties(prefix = "student") 将配置绑定到字段 schoolName 时,以下哪个 yml 写法可以被正确绑定?

A. student.school-name: xxx B. student.school_name: xxx C. student.schoolName: xxx D. 以上都可以

答案:D

【考点】@ConfigurationProperties 的宽松绑定(Relaxed Binding)规则。

【结论】选 D。@ConfigurationProperties 支持宽松绑定,kebab-case、snake_case、camelCase 三种写法均可正确映射到 schoolName 字段。

【逐项辨析】

  • A. 正确。 kebab-case(短横线连接)是 Spring Boot 官方推荐的外部配置书写风格,可被宽松绑定解析。
  • B. 正确。 snake_case(下划线连接)同样属于宽松绑定支持的命名风格。
  • C. 正确。 camelCase(驼峰命名)与 Java 字段名完全一致,当然可以被绑定。
  • D. 正确。 由于 A、B、C 均可独立成立,故“以上都可以”是最终答案。

【知识点】 宽松绑定(Relaxed Binding)是 @ConfigurationProperties 的核心特性之一,其规则如下:

  1. 目标字段命名:绑定到 Java 字段时,字段名本身的驼峰格式会被拆解为单词片段(school + name)。
  2. 源属性匹配:配置源(yml / properties / 环境变量)中的属性名可以是 kebab-case(school-name)、snake_case(school_name)、camelCase(schoolName)或 大写下划线(SCHOOL_NAME,环境变量常见)。
  3. 大小写不敏感:匹配过程中不区分大小写,且分隔符(-、_、空)被视为等价。
  4. @Value 的限制:@Value("${student.schoolName}") 不支持宽松绑定,必须严格匹配属性名。这也是推荐使用 @ConfigurationProperties 进行批量配置绑定的重要原因。

此外,若 prefix 本身包含多层,如 app.student-info,则对应字段名 studentInfo 也能通过宽松绑定正确映射。

【记忆锚点】 “中划线、下划线、驼峰线,宽松绑定三通吃;@Value 是死脑筋,只认原配不认亲戚。”

【易混对比】

对比维度@ConfigurationProperties@Value
松散绑定支持 kebab / snake / camel / 大写下划线不支持,必须严格匹配
批量绑定支持,整个对象一次性注入不支持,需逐个字段指定
类型转换支持复杂类型(List、Map、Duration)基础类型简单转换
JSR-303 校验可与 @Validated + @NotNull 等联用不直接支持
SpEL 表达式不支持支持 ${} 及 #{}
使用场景结构化配置、多字段绑定单个属性、动态表达式

换问法: 若题目问“以下哪种写法不能被 @Value(”${student.schoolName}“) 正确解析?”,则 student.school-name 和 student.school_name 均无法被识别,只有严格匹配 student.schoolName 才可以。

【自测】

某配置类使用 @ConfigurationProperties(prefix = "mail") 绑定字段 smtpHost,以下哪些 properties 写法可以成功注入? ① mail.smtp-host=xxx ② mail.smtp_host=xxx ③ mail.smtphost=xxx ④ mail.SMTP_HOST=xxx

答:①②③④ 四种写法全部可以成功注入,其中就包括 ③ mail.smtphost。宽松绑定比较属性名时用的是 uniform 形式:先去掉 -、_ 等分隔符,再忽略大小写做比对,因此 smtp-host、smtp_host、smtphost、SMPTHOST、smtpHost 在绑定阶段是同一个名字;并不存在「单词边界必须有分隔符」这条规则——原答案把 ③ 判为无法匹配,方向正好说反了。

text
SpringBoot = 3.4.6,目标字段 smtpHost(Binder + MapConfigurationPropertySource 实测)
mail.smtp-host  ->  绑定成功: smtpHost=smtp.example.com
mail.smtp_host  ->  绑定成功: smtpHost=smtp.example.com
mail.smtphost   ->  绑定成功: smtpHost=smtp.example.com   ← 旧答案误判为「无法匹配」
mail.SMTP_HOST  ->  绑定成功: smtpHost=smtp.example.com
mail.SMTPHOST   ->  绑定成功: smtpHost=smtp.example.com
mail.smtpHost   ->  绑定成功: smtpHost=smtp.example.com

复现方式:new Binder(new MapConfigurationPropertySource(map)).bind("mail", Mail.class),map 中每次只放上面一个 key。另需分清边界:宽松绑定只服务于 @ConfigurationProperties,@Value("${mail.smtp-host}") 必须与配置文件里写的名字严格一致(见本题【知识点】第 4 条)。与第 S06 题连考。

【知识关联】

  • 同库关联:与 S04;@ConfigurationProperties vs @Value。
  • 实现层:宽松绑定(kebab/camel/relaxed);JSR-303 校验可选。
  • 面试追问:① 与 @Value 差异?② 前缀规范?

【拓展延伸】

  • 变式问法:app.user-name 绑定 userName。
  • 版本差异:宽松绑定规则 Boot2/3 有收紧趋势,命名尽量规范。
  • 工程注意点:结构化配置用 @ConfigurationProperties + POJO;加 @Validated。

S06 · 知识点:Profile 多环境配置 ​

【题目】在 application.yml 中设置 spring.profiles.active=prod 后,Spring Boot 启动时会加载哪些配置文件?

A. 仅加载 application-prod.yml B. 启动报错,需删除默认配置文件 C. 仅加载 application.yml D. 加载 application.yml 与 application-prod.yml,且 prod 文件覆盖默认配置

答案:D

【考点】Profile 的加载机制与同名配置覆盖关系。

【结论】选 D。Spring Boot 会同时加载默认配置文件 application.yml 和 Profile 专属文件 application-prod.yml,并以 Profile 文件中的同名配置覆盖默认配置。

【逐项辨析】

  • A. 错误。 不会仅加载 Profile 文件,默认配置是全局基础,始终会被加载。
  • D. 正确。 默认配置与 Profile 配置合并加载,Profile 专属文件在合并层中优先级更高,实现同名属性覆盖。
  • C. 错误。 若仅加载默认配置,则 Profile 机制失去意义,无法完成多环境隔离。
  • B. 错误。 Spring Boot 允许并鼓励同时保留默认配置和多个 Profile 文件,框架会正常处理,不会报错。

【知识点】 Profile 机制的设计哲学是“默认打底、环境覆盖”。具体流程如下:

  1. 解析 active profiles:Spring Boot 从 spring.profiles.active(或 spring.profiles.default)获取待激活的 Profile 列表,支持多 Profile 同时激活(如 dev,test)。
  2. 加载默认配置:首先加载 application.properties / application.yml 中的非 Profile 特定配置。
  3. 加载 Profile 专属配置:按顺序加载 application-{profile}.properties / application-{profile}.yml。若多个 Profile 同时激活,后加载的 Profile 优先级更高(会覆盖前者同名属性)。
  4. 合并与覆盖:所有配置来源按 Spring Boot 的优先级规则合并成最终的 Environment,Profile 专属配置处于较高层级。

补充:spring.config.activate.on-profile(在单文件内分隔 Profile 块)与 spring.profiles.group(将多个 Profile 组合为一个逻辑组)都是 Spring Boot 2.4 随配置文件处理机制重构一起引入的——2.4 同时引入了 spring.config.import、把多文档块里旧的 spring.profiles: xxx 写法换成了 spring.config.activate.on-profile(见本题【拓展延伸】「2.4+ 配置文件处理有 breaking change」)。不是 3.1 才有的特性,3.x 只是延续 2.4+ 的这套模型;把版本写成 3.1 与本题【拓展延伸】自相矛盾,应以 2.4 为准。

【记忆锚点】 “默认打底,Profile 盖楼;active 是开关,同名后者优。”

【易混对比】

对比维度默认配置(application.yml)Profile 专属配置(application-prod.yml)
加载时机始终加载仅在对应 Profile 激活时加载
优先级较低较高(覆盖默认同名项)
用途存放通用、跨环境共享配置存放环境特定配置(如数据库地址、日志级别)
是否必须推荐保留按需创建
属性作用
spring.profiles.active显式激活指定 Profile(最高优先级)
spring.profiles.default未指定 active 时的默认兜底 Profile
spring.profiles.include在已激活 Profile 基础上追加额外 Profile
spring.profiles.group将多个 Profile 绑定为一个组名

换问法: 若题目问“同时激活 dev 和 prod 两个 Profile,且二者存在同名属性,最终以谁为准?”,答案为后加载的 Profile(取决于声明顺序,通常后声明的优先级更高)。

【自测】

某项目 application.yml 中配置了 spring.profiles.active: dev,mysql,且存在 application-dev.yml 和 application-mysql.yml。若 application-dev.yml 中 app.name=dev-app,application-mysql.yml 中 app.name=mysql-app,最终 Environment 中 app.name 的值是多少?

答:mysql-app。多个 Profile 同时激活时,后加载的 Profile(mysql)优先级高于先加载的(dev),同名属性以后者为准。与第 S07 题连考。

【知识关联】

  • 同库关联:与 S04;spring.profiles.active / include。
  • 实现层:Profile 条件激活不同 PropertySource 与 @Profile Bean。
  • 面试追问:① 如何激活?② 生产默认 profile?

【拓展延伸】

  • 变式问法:@Profile("dev");启动参数 --spring.profiles.active=prod。
  • 版本差异:2.4+ 配置文件处理有 breaking change(spring.config.import 等)。
  • 工程注意点:禁止把密钥打进默认配置;配置差异用 profile 拆分。

S07 · 知识点:内嵌 Web 容器 ​

【题目】spring-boot-starter-web 默认引入的内嵌 Web 容器及其默认端口是? A. Jetty,8080 B. Tomcat,8080 C. Undertow,8081 D. Tomcat,8081

答案:B

【考点】Spring Boot 默认内嵌容器与默认端口配置。

【结论】选 B。spring-boot-starter-web 默认引入内嵌 Tomcat,默认监听端口为 8080。

【逐项辨析】

  • A. 错误。 Jetty 是可选替换容器,非默认引入。
  • B. 正确。 spring-boot-starter-web 的传递依赖中默认包含 spring-boot-starter-tomcat,且 server.port 默认值是 8080。
  • C. 错误。 Undertow 同样是可选替换容器,且默认端口不是 8081。
  • D. 错误。 容器正确但端口错误,Spring Boot 默认端口为 8080,除非显式修改。

【知识点】 spring-boot-starter-web 的依赖树核心路径为:

spring-boot-starter-web
  └── spring-boot-starter-tomcat
        └── tomcat-embed-core / tomcat-embed-websocket

如需切换容器,需在 pom.xml 中排除 Tomcat 并引入替代 starter:

  • 切换 Jetty:排除 spring-boot-starter-tomcat,引入 spring-boot-starter-jetty。
  • 切换 Undertow:排除 spring-boot-starter-tomcat,引入 spring-boot-starter-undertow。

端口配置方式:

  • server.port=9090(properties)
  • server.port: 9090(yml)
  • 命令行 --server.port=9090
  • 随机端口:server.port=0(实际端口在运行时分配,可通过 @LocalServerPort 获取)

此外,server.* 命名空间还包含 server.servlet.context-path、server.tomcat.max-threads 等大量容器调优参数。

【记忆锚点】 “Web 默认 Tomcat,端口 8080 忘不掉;想换 Jetty 或 Undertow,先排 Tomcat 再引包。”

【易混对比】

容器特点适用场景
Tomcat默认、成熟、社区最大、Servlet 规范完整实现通用场景,开发首选
Jetty轻量、异步支持好、嵌入式历史悠久长连接、WebSocket、高并发
Undertow基于 XNIO、内存占用低、支持阻塞/非阻塞混合资源受限、微服务、云原生
配置项默认值说明
server.port8080HTTP 监听端口
server.servlet.context-path(无默认值,等价于 /)元数据里该项没有默认值,不配即根路径
server.tomcat.threads.max200Tomcat 最大线程数
server.jetty.threads.max200Jetty 最大线程数
server.undertow.threads.workerCPU 核心数 × 8Undertow 工作线程数

换问法: 若题目问“如何在不修改配置文件的情况下将端口临时改为 9090?”,答案应为启动时传入命令行参数 --server.port=9090。

【自测】

某微服务需要在一台机器上启动多个实例,要求每个实例自动分配不同端口且互不冲突,应如何配置?又如何获取实际分配的端口?

答:在配置文件中设置 server.port=0,Spring Boot 会委托操作系统分配一个可用临时端口;在代码中通过 @LocalServerPort(或 @Value("${local.server.port}"))注入实际端口号,或从 WebServerApplicationContext 中获取。与第 S08 题连考。

【知识关联】

  • 同库关联:与 S08;内嵌 Tomcat/Jetty/Undertow。
  • 实现层:Servlet 容器作为依赖自动配置;spring-boot-starter-web 默认 Tomcat。
  • 面试追问:① 如何换成 Jetty?② 打 war 外置容器?

【拓展延伸】

  • 变式问法:排除 tomcat 依赖引入 jetty。
  • 版本差异:Boot3 对应 Servlet 6 / Tomcat 10+(Jakarta)。
  • 工程注意点:容器版本与 Boot 版本匹配;K8s 探针与优雅停机。

S08 · 知识点:Spring Boot 启动流程 ​

【题目】关于 SpringApplication.run() 的启动流程,以下顺序正确的是?

A. 推断应用类型 → 创建 SpringApplication → 准备 Environment → 创建容器 → refresh B. 创建 SpringApplication → 推断应用类型 → 准备 Environment → 创建并刷新容器 → 启动完成 C. 创建容器 → 推断应用类型 → 创建 SpringApplication → 加载配置 D. 以上顺序均不正确

答案:B

【考点】Spring Boot 启动流程的主要阶段顺序。

【结论】选 B。完整顺序为:创建 SpringApplication 实例(内部推断应用类型)→ 准备 Environment → 创建 ApplicationContext → refresh 刷新容器 → 启动完成。

【逐项辨析】

  • A. 错误。 推断应用类型发生在 new SpringApplication() 的构造过程中,而非创建实例之前,整体顺序描述不准确。
  • B. 正确。 main 方法中先 new SpringApplication()(构造阶段推断应用类型、加载初始化器和监听器),再调用 run() 完成环境准备、容器创建与刷新、Runner 执行。
  • C. 错误。 创建容器是 run() 方法中较后的步骤,不可能先于 SpringApplication 的创建。
  • D. 错误。 选项 B 的描述符合官方源码逻辑。

【知识点】 Spring Boot 启动流程可细分为两大阶段:

阶段一:构造 SpringApplication

  1. 推断应用类型(WebApplicationType):
    • SERVLET:classpath 存在 jakarta.servlet.Servlet(Boot 3 / Framework 6 已切到 Jakarta 命名空间,实测 WebApplicationType 常量池里只有 jakarta/servlet/Servlet,无 javax)且不存在 Reactive Web 栈。老Boot 2.x 才是 javax.servlet.Servlet。
    • REACTIVE:classpath 存在 org.springframework.web.reactive.DispatcherHandler 且不存在 Servlet。
    • NONE:以上均不满足。
  2. 加载 ApplicationContextInitializer 和 ApplicationListener(通过 SpringFactoriesLoader 读取 META-INF/spring.factories)。
  3. 推断主类(main 方法所在类)。

阶段二:执行 run() 方法

  1. 获取并启动 SpringApplicationRunListeners(发布 ApplicationStartingEvent)。
  2. 准备 Environment(加载配置文件、解析命令行参数、绑定到 Spring 环境)。
  3. 打印 Banner(默认 Spring Boot 图案,可自定义)。
  4. 创建 ApplicationContext(根据应用类型创建 AnnotationConfigServletWebServerApplicationContext 或 Reactive 对应上下文)。
  5. 准备 Context(执行 Initializer、加载源配置类、发布 ApplicationPreparedEvent)。
  6. 刷新 Context(refresh()):
    • 注册 BeanDefinition
    • 触发自动配置(@EnableAutoConfiguration)
    • 实例化单例 Bean
    • 启动内嵌 Web 服务器(若存在)
  7. 执行 CommandLineRunner 和 ApplicationRunner。
  8. 发布 ApplicationReadyEvent,启动完成。

【记忆锚点】 “先建应用再推断,准备环境后刷新;Runner 最后跑起来,Ready 事件表完成。”

【易混对比】

应用类型判断条件创建的上下文
SERVLET存在 Servlet API,无 ReactiveAnnotationConfigServletWebServerApplicationContext
REACTIVE存在 WebFlux,无 ServletAnnotationConfigReactiveWebServerApplicationContext
NONE无 Web 依赖AnnotationConfigApplicationContext
阶段关键动作对应事件
构造期推断类型、加载 Initializer/ListenerApplicationStartingEvent
环境准备加载配置文件、解析参数ApplicationEnvironmentPreparedEvent
Context 准备执行 Initializer、注册主类ApplicationPreparedEvent
Context 刷新自动配置、实例化 Bean、启动 Web 容器—
启动完成执行 RunnerApplicationReadyEvent / ApplicationFailedEvent

换问法: 若题目问“在 Spring Boot 启动过程中,内嵌 Tomcat 是在哪个阶段被启动的?”,答案应为 refresh Context 阶段(具体在 onRefresh() 或 finishRefresh() 中通过 WebServerStartStopLifecycle 启动)。

【自测】

如何在 Spring Boot 启动完成后、接收第一个 HTTP 请求前,执行一段初始化逻辑(如缓存预热、数据加载)?请写出代码片段并说明原理。

答:实现 ApplicationRunner 或 CommandLineRunner 接口,将其注册为 Bean,Spring Boot 在容器刷新完成后会自动回调。示例:

java
@Component
public class CacheWarmUpRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        // 执行缓存预热逻辑
    }
}

原理:SpringApplication.run() 在 refreshContext 之后、发布 ApplicationReadyEvent 之前,会遍历容器中所有 Runner 并依次调用。与第 S01 题连考。

【知识关联】

  • 同库关联:与 S01/S02;SpringApplication.run 阶段。
  • 实现层:准备 Environment → 创建 Context → 刷新(加载配置、自动配置、Bean) → Runner 回调。
  • 面试追问:① 监听器有哪些扩展点?② 启动慢如何排查?

【拓展延伸】

  • 变式问法:ApplicationRunner/CommandLineRunner 区别。
  • 版本差异:细节随版本演进。
  • 工程注意点:避免在 @PostConstruct 做重活;启动分析用 actuator startup。

持续学习,持续积累。