Spring Boot 面试题全解析:18 道核心高频题(附详细分析)
18 道最常见的 Spring Boot 面试题逐题详解,从自动配置原理到源码链路分析、参数校验、全局异常处理,附代码示例和 Deep Dive 解析。
Spring Boot 面试题全解析:18 道核心高频题
本文整理自 JavaGuide《Java面试指北》,结合 Spring Boot 官方文档(v4.1.0)和源码分析,逐题深入解析。
Q1: 简单介绍一下 Spring?有什么缺点?
Spring 是重量级企业开发框架 EJB 的替代品,通过依赖注入(IOC) 和面向切面编程(AOP),用简单的 POJO 实现了 EJB 的功能。
Spring 让 Java 开发从"重量级容器"转向"轻量级 POJO 编程"。你只需要写一个普通的 Java 类,加上 @Service 或 @Component,Spring 就能帮你管理它的生命周期和依赖关系。
缺点一:配置过于繁琐。 虽然 Spring 一直在改进——2.5 引入注解扫描、3.0 引入 Java Config——但事务管理、Spring MVC、第三方库集成等场景仍然需要显式配置。
缺点二:依赖管理头疼。 不同库之间的版本兼容性需要人工维护,版本选错就各种 ClassNotFoundException。
Q2: 为什么要有 Spring Boot?
Spring Boot 旨在简化 Spring 开发——减少配置文件,实现开箱即用。
Spring Boot 不是代替 Spring,而是在 Spring 上面包了一层自动配置层。它把 Spring 中繁琐的配置工作封装为"自动配置",把依赖管理封装为"Starter",让开发者只需要关注业务代码。
类比:Spring 像一套精装的毛坯房(框架给你了,但水电、墙面、地板都得自己配)。Spring Boot 像精装房——拎包入住。
Q3: Spring Boot 的主要优点
1. 开发非常简单。 不再需要配 web.xml、DispatcherServlet,一个 @SpringBootApplication 就能启动项目。
2. 开发时间明显减少。 样板代码大幅减少,纯 Spring 搭建 Web 项目的代码量可能是 Spring Boot 的 5 倍。
3. 不需要大量 XML 配置。 没有 web.xml、applicationContext.xml、spring-mvc.xml。
4. 容易与 Spring 生态集成。 想用 Spring Security?加 spring-boot-starter-security。想用 Spring Data JPA?加 spring-boot-starter-data-jpa。
5. 固执己见的默认配置(Opinionated Defaults)。 Spring Boot 替你做了一组最佳实践选择:内嵌容器用 Tomcat、JSON 处理用 Jackson、连接池用 HikariCP、日志用 Logback。想换随时能换。
6. 内嵌 Servlet 容器。 直接执行 main 方法就能启动 Web 项目,开发和测试时不需要在电脑上单独装 Tomcat。
7. CLI 工具。 可以用 Groovy 脚本快速开发原型。
8. Maven/Gradle 插件。 spring-boot-maven-plugin 可以把项目打包成可执行的 fat jar。
Q4: 什么是 Spring Boot Starters?
Starters 是一组依赖关系的集合,把某个场景所需的全部依赖打包在一起。
没有 Starter 之前,做 Web 项目需要手动加 spring-webmvc、tomcat-embed-core、jackson-databind、hibernate-validator、spring-boot-autoconfigure 等等,还要自己检查版本兼容性。
有了 Starter,只需要一行:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这一个依赖里包含了上述全部依赖,版本由 Spring Boot BOM 统一管理。
每个 Starter 命名规范:spring-boot-starter-{模块名}。常见的有:spring-boot-starter-web、spring-boot-starter-data-jpa、spring-boot-starter-data-redis、spring-boot-starter-amqp、spring-boot-starter-security。
Q5: Spring Boot 支持哪些内嵌 Servlet 容器?
| 容器 | 默认? | 特点 |
|---|---|---|
| Tomcat | ✅ 默认 | 稳定,社区最活跃 |
| Jetty | ❌ | 更轻量,适合嵌入式场景 |
| Undertow | ❌ | 高并发性能好,WildFly 项目维护 |
Spring Boot 把这些容器的嵌入式版本打包进了 jar。你执行 main 方法 → Spring Boot 启动内嵌容器 → 把应用部署到容器里。
Q6: 如何换成 Jetty?
在 pom.xml 中排除默认的 Tomcat,加上 Jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
延伸: 一般用默认的 Tomcat 就够了。换 Jetty 通常是内存敏感型应用(嵌入式设备 / IoT),换 Undertow 则是高并发场景。
Q7: 介绍 @SpringBootApplication 注解
@SpringBootApplication 是三个注解的复合注解:
| 注解 | 作用 |
|---|---|
@Configuration(实际是 @SpringBootConfiguration) | 标记当前类为配置类,允许写 @Bean 方法 |
@EnableAutoConfiguration | 开启自动配置机制——Spring Boot 的灵魂 |
@ComponentScan | 扫描当前包及其子包,注册 @Component 等 Bean |
源码验证:
@SpringBootConfiguration // 里面带 @Configuration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication { }
常见追问: 如果启动类在 com.example 包,Service 类在 org.other 包,能扫到吗?不能。@ComponentScan 默认只扫当前包。如果需扫其他包,加参数 @ComponentScan("org.other")。
Q8: Spring Boot 的自动配置如何实现?
这是 Spring Boot 最核心的面试题,完整链路如下:
@SpringBootApplication
→ @EnableAutoConfiguration
→ @Import(AutoConfigurationImportSelector.class)
→ 读取 AutoConfiguration.imports 文件
→ 拿到 100+ 个 *AutoConfiguration 配置类
→ 逐条检查 @ConditionalOnXxx 条件注解
→ 条件满足 → 自动装配
逐层拆解:
第 1 层:@EnableAutoConfiguration 源码非常简洁:
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
通过 @Import 引入 AutoConfigurationImportSelector,把决定权交给这个类。
第 2 层:读取 .imports 文件
AutoConfigurationImportSelector.getCandidateConfigurations() 去 classpath 找文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容是一长串全限定类名:
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
...(100 多个)
第 3 层:条件检查——@Conditional
不是全部启用,而是逐个检查 @Conditional 条件:
| 注解 | 判断条件 |
|---|---|
@ConditionalOnClass | classpath 存在指定类 |
@ConditionalOnMissingBean | 容器中没有指定 Bean |
@ConditionalOnBean | 容器中存在指定 Bean |
@ConditionalOnProperty | 配置中有指定属性 |
@ConditionalOnWebApplication | 当前是 Web 应用 |
举例:
@Configuration
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RedisTemplate redisTemplate() { ... }
}
你引入了 spring-boot-starter-data-redis → classpath 多了 Jedis → @ConditionalOnClass 满足 → RedisTemplate 自动创建。
总结一句话:自动配置 = 先读配置文件清单 + 再按条件决定是否生效。
Q9: 开发 RESTful Web 服务常用注解
Bean 相关:
| 注解 | 层级 | 作用 |
|---|---|---|
@RestController | Controller | @Controller + @ResponseBody,直接返回 JSON |
@Service | Service | 服务层,写业务逻辑 |
@Repository | DAO | 持久层,数据库操作;异常自动转换 |
@Autowired | 通用 | 依赖注入 |
@Component | 通用 | 无法归类的组件 |
HTTP 方法映射:
| 注解 | HTTP 方法 |
|---|---|
@GetMapping | GET |
@PostMapping | POST |
@PutMapping | PUT |
@DeleteMapping | DELETE |
参数传值:
| 注解 | 获取来源 | 适用场景 |
|---|---|---|
@PathVariable | URL 路径 | /user/{id} |
@RequestParam | URL 查询参数 | /user?id=1 |
@RequestBody | 请求体 | POST JSON |
常见追问: @Controller 和 @RestController 的区别?@Controller 返回视图名(配合模版引擎),@RestController 直接返回 JSON。现在的微服务几乎都用 @RestController。
Q10: Spring Boot 的两种配置文件类型
application.properties 和 application.yml。两个文件可以共存,properties 优先级高于 yml。但在一个项目中通常只选一种。
Q11: YAML 的优势和缺点
优势: YAML 采用分层结构,比 properties 的扁平点号分隔更直观。
# YAML
server:
port: 8080
tomcat:
max-threads: 200
# properties 等效
server.port=8080
server.tomcat.max-threads=200
多层嵌套时 YAML 的优势更加明显。
缺点: YAML 不支持 @PropertySource 注解导入自定义配置文件。
// 可以
@PropertySource("classpath:custom.properties")
// 不行
@PropertySource("classpath:custom.yml") // 报错
Q12: 读取配置文件的三种方法
方法一:@Value(读单个值)
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.version:1.0.0}") // 冒号后是默认值
private String appVersion;
}
适合只需要读取一两个配置项的场景。缺点:每个字段都需要写 @Value。
方法二:@ConfigurationProperties(批量绑定,推荐)
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
private List<String> servers;
// 必须有 getter/setter
}
对应 yml:
app:
name: my-app
version: 2.0.0
servers:
- dev-server
- prod-server
优势:类型安全、批量绑定、支持 @Validated 校验。
方法三:@PropertySource(指定文件)
@Component
@PropertySource("classpath:db.properties")
public class DbConfig {
@Value("${db.url}")
private String url;
}
限制:只支持 .properties,不支持 .yml。
Q13: 配置文件加载优先级
完整优先级(从高到低):
- 命令行参数:
java -jar app.jar --server.port=8081 - OS 环境变量:
SERVER_PORT=8081 - application-{profile}.yml:如
application-dev.yml - application.yml(jar 包外部,同级目录)
- application.yml(jar 包内部,classpath 下)
高优先级覆盖低优先级。部署时用 --server.port=8081 可以覆盖 jar 包里的任何配置。
文件位置优先级(同名的 application.yml):
- 当前目录下的
/config子目录 - 当前目录(jar 包同级)
- classpath 下的
/config - classpath 根目录
这个机制很实用——改配置不用重新打包,在 jar 包外面放一份 application.yml 即可覆盖。
Q14: 常用的 Bean 映射工具有哪些?
项目中经常要把 Entity 转 DTO、DTO 转 VO,同名属性手动 get/set 很繁琐:
UserVO vo = new UserVO();
vo.setId(user.getId());
vo.setName(user.getName());
vo.setEmail(user.getEmail());
// ...10个字段写10行
Bean 映射工具自动拷贝同名属性。
| 工具 | 原理 | 性能 |
|---|---|---|
| Spring BeanUtils | 反射 | 中等 |
| MapStruct | 编译期生成代码 | ⭐ 高(推荐) |
| Apache BeanUtils | 反射 | ❌ 低 |
| ModelMapper | 反射 + 动态代理 | ❌ 低 |
MapStruct 示例:
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 同名属性自动拷贝
UserVO toVo(User user);
// 忽略某个字段
@Mapping(target = "phone", ignore = true)
UserVO toVoWithoutPhone(User user);
}
调用:UserMapper.INSTANCE.toVo(user)。MapStruct 编译期生成实现类,没有反射开销。
Q15: Spring Boot 如何监控系统运行状况?
使用 Spring Boot Actuator。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
常用端点:
| 路径 | 作用 |
|---|---|
/actuator/health | 应用健康状态 |
/actuator/info | 自定义应用信息 |
/actuator/metrics | JVM 指标(内存、CPU、线程) |
/actuator/env | 环境变量 |
/actuator/beans | 所有 Spring Bean 列表 |
/actuator/loggers | 动态调整日志级别 |
实际用法:
GET http://localhost:8080/actuator/health
# 返回:{"status": "UP"}
生产环境中 Actuator 的数据通常配合 Prometheus + Grafana 做可视化监控。
Q16: 如何做请求参数校验?
即使前端做了校验,攻击者也可以绕过浏览器直接发 HTTP 请求。所以后端必须再做一次校验。
spring-boot-starter-web 自带 hibernate-validator(JSR-303 实现),无需额外引入。
常用校验注解:
| 注解 | 作用 |
|---|---|
@NotNull | 不能为 null |
@NotBlank | 字符串不能为 null 且长度 > 0 |
@Email | 邮箱格式 |
@Min/@Max | 数字范围 |
@Size | 长度范围 |
@Pattern | 正则匹配 |
@Past/@Future | 日期校验 |
校验 RequestBody(最常用):
@Data
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@Email(message = "邮箱格式不正确")
@NotBlank(message = "邮箱不能为空")
private String email;
@Min(value = 1, message = "年龄最小为 1")
@Max(value = 150, message = "年龄最大为 150")
private int age;
}
@PostMapping("/users")
public Result createUser(@RequestBody @Valid CreateUserRequest request) {
// 校验失败自动返回 400
return Result.success();
}
校验 PathVariable / RequestParam:
@RestController
@RequestMapping("/api")
@Validated // 必须加在类上
public class UserController {
@GetMapping("/user/{id}")
public Result getUser(@PathVariable @Valid @Min(1) Long id) { ... }
}
Q17: 如何实现全局异常处理?
没有全局异常处理时,每个 Controller 都要写 try-catch,代码重复 10 遍:
@GetMapping("/user/{id}")
public Result getUser(@PathVariable Long id) {
try {
User user = userService.findById(id);
return Result.success(user);
} catch (UserNotFoundException e) {
return Result.error(404, "用户不存在");
} catch (Exception e) {
return Result.error(500, "服务器内部错误");
}
}
使用 @ControllerAdvice + @ExceptionHandler:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusiness(BusinessException e) {
log.error("业务异常", e);
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors().stream()
.map(err -> err.getField() + ": " + err.getDefaultMessage())
.collect(Collectors.joining("; "));
return Result.error(400, msg);
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("未知异常", e);
return Result.error(500, "服务器繁忙,请稍后重试");
}
}
以后每个 Controller 只管抛出异常,异常处理统一收敛到 GlobalExceptionHandler 中。
Q18: 如何实现定时任务?
第一步:启动类开启定时任务支持
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第二步:方法上标注 @Scheduled
@Component
public class ScheduledTasks {
// 固定速率——每 5 秒执行一次
@Scheduled(fixedRate = 5000)
public void task1() {
System.out.println("固定速率执行:" + new Date());
}
// 上次结束后延迟 3 秒
@Scheduled(fixedDelay = 3000)
public void task2() {
System.out.println("固定延迟执行:" + new Date());
}
// Cron 表达式——每天凌晨 2 点
@Scheduled(cron = "0 0 2 * * ?")
public void task3() {
System.out.println("每天凌晨 2 点执行备份");
}
}
三种调度方式:
| 方式 | 说明 | 适用场景 |
|---|---|---|
fixedRate | 固定速率,不管上次是否完成 | 监控、心跳 |
fixedDelay | 上次执行完后延迟指定时间 | 数据同步、报表 |
cron | Cron 表达式,精确到秒 | 定时备份、定时发邮件 |
注意: @Scheduled 默认单线程。多个任务时可配置线程池:
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
总结
这 18 道题从"Spring 的缺点"问到"定时任务",形成了一个完整的 Spring Boot 知识链条。其中最值得深入理解的是 Q8 自动配置原理——它是 Spring Boot 设计哲学的根基。
建议动手操作:
- 打开 IDE,找到项目中的
spring-boot-autoconfigure依赖 jar - 打开
META-INF/spring/AutoConfiguration.imports,看看 100+ 个配置类 - 点开
RedisAutoConfiguration看看 @ConditionalOnClass 怎么写 - 在启动类断点调试,跟踪调用栈到 AutoConfigurationImportSelector