Spring与SpringBoot

Spring与SpringBoot
pfa目录
- 一、Spring 和 Spring Boot 有什么区别
- 自动配置原理(必问)
- 关键条件注解
- 面试回答
- 二、Spring Boot 配置文件优先级
- 三、Spring Boot 怎么接入一个外部组件
- Starter 自动配置原理(面试核心)
- 自定义 Starter 的标准套路(进阶考点)
- 接入外部组件的三种模式总结
- 四、面试高频追问
- 引用
Spring 与 Spring Boot
这篇主要整理 Spring 与 Spring Boot 面试中最常见的几个问题:二者区别、自动配置原理、配置文件优先级、外部组件接入方式,以及面试官常追问的细节。回答时可以先讲结论,再补自动配置和 Starter 机制,这样逻辑会更完整。
一、Spring 和 Spring Boot 有什么区别
一句话
Spring 是框架,Spring Boot 是 Spring 的自动装配层 + 运维工具层,本质是“用约定消灭配置”。
核心区别
| 维度 | Spring | Spring Boot |
|---|---|---|
| 启动方式 | 手动配置 ApplicationContext | @SpringBootApplication 一键启动 |
| 依赖管理 | 手动选版本,版本冲突频发 | Starter BOM 统一版本,引入即用 |
| 配置方式 | 大量 XML / Java Config | 约定大于配置,application.yml 零配置开箱 |
| 内嵌容器 | 需要打 WAR 包部署到 Tomcat | 内嵌 Tomcat/Jetty,java -jar 直接跑 |
| 自动配置 | 无,每个 Bean 手动声明 | @EnableAutoConfiguration 按条件自动装配 |
| 运维能力 | 无内置 | Actuator 健康检查、指标、环境信息 |
| 生产就绪 | 需要大量额外配置 | 开箱即用(日志、监控、外部化配置) |
Spring Boot 并不是替代 Spring,而是在 Spring 之上补齐了工程化能力。它把依赖版本管理、自动配置、内嵌容器、外部化配置和生产监控统一起来,让开发者少写大量重复配置。
自动配置原理(必问)
1 | @SpringBootApplication |
@EnableAutoConfiguration 做了什么:
- 加载
META-INF/spring.factories(Spring Boot 2.x)
或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(3.x) - 读取所有 AutoConfiguration 类
- 每个 AutoConfiguration 类上有
@Conditional系列注解 - 条件满足 → 装配 Bean;条件不满足 → 跳过
可以把自动配置理解成“候选配置列表 + 条件判断”。Spring Boot 先把可能用到的自动配置类加载进来,再根据 classpath、已有 Bean、配置项、Web 环境等条件决定哪些真正生效。
关键条件注解
| 注解 | 作用 |
|---|---|
@ConditionalOnClass | classpath 存在某个类时生效 |
@ConditionalOnMissingBean | 容器中没有该 Bean 时生效 |
@ConditionalOnProperty | 配置项满足条件时生效 |
@ConditionalOnWebApplication | Web 应用时生效 |
举例:引入
spring-boot-starter-data-redis后,classpath 有RedisTemplate.class,RedisAutoConfiguration的@ConditionalOnClass成立,自动创建RedisTemplateBean。没引入则不生效,零成本。
面试回答
Spring 是 IoC + AOP 的框架基础,Spring Boot 在 Spring 之上做了三件事:一是通过 Starter + 自动配置消灭了样板配置,引入
starter-web就有 Tomcat 和 MVC,引入starter-redis就有RedisTemplate;二是内嵌容器让应用从 WAR 部署变成java -jar独立运行;三是 Actuator 提供了生产级运维能力。本质区别不是技术栈不同,而是 Spring Boot 用“约定大于配置”把 Spring 的使用成本降了一个量级。
二、Spring Boot 配置文件优先级
自动配置解决的是“Bean 怎么自动装配”,配置优先级解决的是“同一个属性到底听谁的”。Spring Boot 的外部化配置非常灵活,面试时最容易考 jar 包内外、profile、默认值之间的覆盖关系。
优先级从高到低(完整版)
- 命令行参数
- JNDI 属性
- Java 系统属性
- 操作系统环境变量
- random.* 属性
- jar 包外
application-{profile}.yml← 最常用 - jar 包内
application-{profile}.yml - jar 包外
application.yml← 最常用 - jar 包内
application.yml @PropertySource注解指定的配置SpringApplication.setDefaultProperties()的默认值
高频考点:jar 包外 > jar 包内
1 | myapp/ |
完整的外部配置覆盖顺序(jar 外)
config/application-{profile}.yml ← 最高
同一层级内,profile 配置永远覆盖默认配置。
高频考点:properties vs yml 优先级
同一位置同时存在 application.properties 和 application.yml:
- properties 优先级高于 yml
- 但实际项目不应同时存在,容易混乱
高频考点:多配置文件激活
1 | spring: |
| 属性 | 作用 | 版本 |
|---|---|---|
spring.profiles.active | 激活某个 profile | 全版本 |
spring.profiles.include | 在当前 profile 基础上追加 profile | 全版本 |
spring.profiles.group | 定义 profile 组,激活组名等于激活组内所有 | 2.4+ |
配置优先级的核心规律可以先记三句话:命令行参数优先级最高,外部配置覆盖内部配置,profile 配置覆盖默认配置。再进一步区分代码里的默认值,就能回答大多数追问。
高频考点:配置文件 vs 代码默认值 —— 谁覆盖谁?
结论:配置文件 > 代码默认值,但有两种“代码默认值”,优先级完全不同。
第一种:@ConfigurationProperties 字段初始值 / @Value 冒号默认值
1 | // 写法1:字段初始值 |
这两种默认值的优先级低于所有配置源——只要任何一层配置文件里写了 my.service.timeout=5000,就会覆盖代码默认值。
优先级(高→低):
命令行参数 > 系统属性 > 环境变量 > jar外配置 > jar内配置 > 代码默认值
↑ 最底层
验证:
1 | # application.yml 不配置 my.service.timeout |
第二种:SpringApplication.setDefaultProperties() 设置的默认值
1 | SpringApplication app = new SpringApplication(App.class); |
这个默认值的优先级比所有配置文件都低,甚至低于 @PropertySource。它在优先级列表的最末尾(第 11 位)。
完整优先级图示(关键!)
1 | 高 |
面试易错点:
setDefaultProperties()和@ConfigurationProperties字段默认值是两回事。前者是 Environment 里的一个配置源,后者只是属性绑定阶段的兜底值。
1 | // 我们项目里 DRM 动态配置的写法 |
优先级:
DRM 运行时推送的值 > application.properties 中的值 > 代码默认值"https://antchat.alipay.com"
DRM 推送 > 配置文件 > 代码默认值,三层覆盖,运行时可热更新。
Spring Boot 配置优先级的核心规则是“外部覆盖内部、profile 覆盖默认、命令行覆盖一切”。具体来说:命令行参数优先级最高,然后是系统属性和环境变量;文件配置中,jar 包外覆盖 jar 包内,config 目录覆盖根目录,
application-{profile}.yml覆盖application.yml。关于配置文件和代码默认值的关系——配置文件一定大于代码默认值。但要区分两种“代码默认值”:
@ConfigurationProperties的字段初始值和@Value("${key:default}")的冒号默认值,它们是属性绑定的兜底值,优先级低于所有配置源,任何一层配置文件写了就覆盖;而setDefaultProperties()是 Environment 的一个配置源,优先级排在第 11 位,高于前者但低于所有配置文件。我们项目里用这个机制做环境隔离——代码里写默认值兜底,jar 内配置文件放开发环境值,jar 外 config 目录放生产环境值,DRM 运行时推送覆盖一切。
三、Spring Boot 怎么接入一个外部组件
以接入 Redis 为例,完整链路通常是:引入 Starter、编写连接配置、直接注入使用。这里体现的还是 Spring Boot 的核心思想:依赖存在时自动装配,用户有自定义 Bean 时优先生效。
引入 Starter 做了什么?
- 自动引入
spring-data-redis、lettuce-core(或 jedis)等依赖 - BOM 管理版本,不需要手动指定
第二步:配置连接
1 | spring: |
第三步:直接使用
1 |
|
不需要手动创建 RedisTemplate Bean——RedisAutoConfiguration 已经自动配好了。
Starter 自动配置原理(面试核心)
引入 spring-boot-starter-data-redis
→ classpath 出现 RedisTemplate.class
→ spring.factories 声明了 RedisAutoConfiguration
→ @ConditionalOnClass(RedisTemplate.class) 成立
→ @ConditionalOnMissingBean(RedisTemplate.class) 成立(用户没自定义)
→ 自动创建 RedisTemplate Bean
如果用户自己定义了 RedisTemplate Bean 呢?
@ConditionalOnMissingBean不成立 → 自动配置跳过 → 用户自定义的生效。这就是 Spring Boot 的“用户优先”原则。
如果面试官继续追问“公司内部组件怎么接入”,就可以从使用 Starter 过渡到自定义 Starter。标准答案不是把所有 Bean 都手写在业务项目里,而是把可复用的装配逻辑沉淀成自动配置。
自定义 Starter 的标准套路(进阶考点)
面试官问“你自己写过 Starter 吗”,用这个结构回答:
1. 项目结构
1 | my-spring-boot-starter/ |
2. 配置属性类
1 |
|
3. 核心服务
1 | public class MyService { |
4. 自动配置类
1 |
|
5. 注册自动配置
Spring Boot 3.x:
1 | # META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
Spring Boot 2.x:
1 | # META-INF/spring.factories |
6. 使用方引入
1 | <dependency> |
1 | my: |
接入外部组件的三种模式总结
| 模式 | 做法 | 适用场景 | 举例 |
|---|---|---|---|
| Starter 自动配置 | 引入 Starter + 配置文件 | 社区有现成 Starter | Redis、MyBatis、Kafka |
| 自定义 Starter | 写 AutoConfiguration + 注册 | 内部中间件、公司组件 | SOFA Boot Starter、内部 SDK |
| 手动集成 | @Configuration + @Bean | 无 Starter、需要精细控制 | 自研组件、老系统对接 |
接入外部组件分三种情况。如果社区有 Starter,引入依赖加配置就行,Spring Boot 的自动配置会通过
@ConditionalOnClass检测 classpath,条件满足就自动创建 Bean。如果是我们内部的中间件,我会按 Starter 规范自己写一个——定义@ConfigurationProperties绑定配置,写@AutoConfiguration类用@ConditionalOnMissingBean保证用户可覆盖,注册到AutoConfiguration.imports。如果组件没有 Starter 也不值得封装,就手动写@Configuration+@Bean。核心原则是“约定大于配置,用户定义优先于自动配置”。
四、面试高频追问
前面几个问题覆盖了主线,下面这些是面试里常见的追问点。准备时重点记住每个问题的“一句话结论”,再补一两个关键细节即可。
4.1 @Conditional 系列注解有哪些?
| 注解 | 条件 |
|---|---|
@ConditionalOnClass | classpath 存在指定类 |
@ConditionalOnMissingClass | classpath 不存在指定类 |
@ConditionalOnBean | 容器中存在指定 Bean |
@ConditionalOnMissingBean | 容器中不存在指定 Bean |
@ConditionalOnProperty | 配置属性满足条件 |
@ConditionalOnWebApplication | 是 Web 应用 |
@ConditionalOnNotWebApplication | 不是 Web 应用 |
@ConditionalOnExpression | SpEL 表达式为 true |
最常用的是 @ConditionalOnClass(检测依赖是否存在)和 @ConditionalOnMissingBean(用户可覆盖)。
4.2 Spring Boot 2.x 和 3.x 自动配置注册的区别?
| Spring Boot 2.x | Spring Boot 3.x | |
|---|---|---|
| 注册文件 | META-INF/spring.factories | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
| 注解 | @Configuration | @AutoConfiguration(新注解,支持 after / before 排序) |
| 条件注解 | 同 3.x | 同 2.x,但部分移到 spring-boot-autoconfigure 新包路径 |
迁移:2.x 的 spring.factories 在 3.x 仍然兼容但已 deprecated,建议迁移到新文件。
4.3 application.yml 和 bootstrap.yml 的区别?
| application.yml | bootstrap.yml | |
|---|---|---|
| 加载时机 | 应用上下文创建时 | 引导上下文(Bootstrap Context)创建时,更早 |
| 用途 | 应用自身配置 | 配置中心地址、加解密配置 |
| Spring Cloud | 后加载 | 先加载,用于拉取远程配置 |
| Spring Boot 单体 | 唯一配置文件 | 不需要 |
Spring Cloud 2020+ 默认禁用了 bootstrap 上下文,改用 spring.config.import 导入配置中心。
4.4 怎么禁用某个自动配置?
1 | // 方式1:排除指定自动配置类 |
1 | # 方式2:配置文件 |
1 | # 方式3:@ConditionalOnProperty 控制开关 |
4.5 SpringFactoriesLoader 机制?
1 | SpringFactoriesLoader.loadFactoryNames(type, classLoader) |
这是 Spring Boot 自动配置的底层机制——@EnableAutoConfiguration 通过 SpringFactoriesLoader 扫描所有 jar 包的 spring.factories,找到 EnableAutoConfiguration key 对应的所有自动配置类。
Spring Boot 3.x 改用 AutoConfiguration.imports 文件,但底层思想一致:约定文件路径 + 全限定类名列表 = SPI 机制。
4.6 Spring Boot 启动流程?
1 | main() |
启动流程可以和前面的自动配置、配置优先级串起来理解:配置文件在准备 Environment 阶段加载,自动配置在刷新上下文阶段参与 Bean 注册,最终完成依赖注入和应用启动。
4.7 你项目里怎么接入 SOFA 中间件的?
通过 SOFABoot Starter。比如接入 AntLLM,引入 sofa-ai-antllm-sofa-boot-starter,在 application.properties 配置 sofa.ai.antllm.chat.base-url 和 sofa.ai.antllm.chat.app-token,Starter 自动创建 AntLlmChatClient Bean,直接 @Autowired 注入使用。
接入 DRM 动态配置,引入 drm-client,用 @DrmResource 注解标记配置类,运行时通过 DRM 控制台热更新,不需要重启。本质和 Spring Boot 官方 Starter 一样,只是蚂蚁内部的中间件封装。







