跳到正文
Joeplover
技术·2026-06-28·约 11 分钟阅读

Spring Boot 面试题全解析:18 道核心高频题(附详细分析)

18 道最常见的 Spring Boot 面试题逐题详解,从自动配置原理到源码链路分析、参数校验、全局异常处理,附代码示例和 Deep Dive 解析。

Spring Boot 代码特写,绿色背景上的 Java 代码,后端开发技术

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 条件:

注解判断条件
@ConditionalOnClassclasspath 存在指定类
@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 相关:

注解层级作用
@RestControllerController@Controller + @ResponseBody,直接返回 JSON
@ServiceService服务层,写业务逻辑
@RepositoryDAO持久层,数据库操作;异常自动转换
@Autowired通用依赖注入
@Component通用无法归类的组件

HTTP 方法映射:

注解HTTP 方法
@GetMappingGET
@PostMappingPOST
@PutMappingPUT
@DeleteMappingDELETE

参数传值:

注解获取来源适用场景
@PathVariableURL 路径/user/{id}
@RequestParamURL 查询参数/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: 配置文件加载优先级

完整优先级(从高到低):

  1. 命令行参数:java -jar app.jar --server.port=8081
  2. OS 环境变量:SERVER_PORT=8081
  3. application-{profile}.yml:如 application-dev.yml
  4. application.yml(jar 包外部,同级目录)
  5. application.yml(jar 包内部,classpath 下)

高优先级覆盖低优先级。部署时用 --server.port=8081 可以覆盖 jar 包里的任何配置。

文件位置优先级(同名的 application.yml):

  1. 当前目录下的 /config 子目录
  2. 当前目录(jar 包同级)
  3. classpath 下的 /config
  4. 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/metricsJVM 指标(内存、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上次执行完后延迟指定时间数据同步、报表
cronCron 表达式,精确到秒定时备份、定时发邮件

注意: @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 设计哲学的根基。

建议动手操作:

  1. 打开 IDE,找到项目中的 spring-boot-autoconfigure 依赖 jar
  2. 打开 META-INF/spring/AutoConfiguration.imports,看看 100+ 个配置类
  3. 点开 RedisAutoConfiguration 看看 @ConditionalOnClass 怎么写
  4. 在启动类断点调试,跟踪调用栈到 AutoConfigurationImportSelector