返回博客列表Back to Blog

SpringBoot 开发踩坑记录:避坑指南与实战总结

2026-07-06 · 踩坑记录 · 13 min read

在 SpringBoot 开发过程中,即便有框架的自动配置加持,新手甚至有经验的开发者也难免会遇到各种“坑”。这些问题往往看似简单,却能耗费大量排查时间。本文汇总了我在近期开发中遇到的高频踩坑场景,结合实战案例分析原因并给出解决方案,希望能帮大家避坑提效。

一、配置类踩坑:自动配置失效与注解使用误区

踩坑1:@Configuration 注解遗漏,自定义配置不生效

场景:在编写自定义配置类(如 Redis、线程池配置)时,写完配置方法后,启动项目发现配置未生效,相关 Bean 无法注入。排查后发现,配置类上忘记添加 @Configuration 注解。

原因:SpringBoot 的自动配置本质是通过扫描带有 @Configuration 注解的类,加载其中的 @Bean 方法来注入实例。若未添加该注解,配置类无法被 Spring 容器识别,其中的 Bean 定义也会被忽略。

解决方案:在自定义配置类上添加 @Configuration 注解,确保 Spring 能扫描并加载该类。若需要开启自动配置的辅助功能,可搭配 @EnableConfigurationProperties 等注解使用。

示例代码:

// 正确写法
@Configuration // 关键注解,不可遗漏
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 省略序列化配置...
        return template;
    }
}

踩坑2:@Value 注解读取配置文件属性为 null

场景:在业务类中使用 @Value("${custom.config.value}") 读取 application.yml 中的自定义属性,启动后发现属性值为 null。

原因:常见有三种情况:一是配置文件中未定义该属性,或属性名拼写错误;二是使用 @Value 的类未被 Spring 管理(未添加 @Component@Service 等注解);三是配置文件格式错误(如 yml 文件缩进问题、属性层级错误)。

解决方案:1. 检查配置文件中属性名与注解中一致,确认属性已正确定义;2. 确保使用 @Value 的类被 Spring 容器扫描并管理;3. 规范 yml 文件缩进(使用2个空格,禁止使用 tab),检查属性层级是否正确。

补充:若属性是必选的,可添加 @Value("${custom.config.value:default}") 设置默认值,避免启动报错;复杂配置建议使用 @ConfigurationProperties 注解,更易维护且支持类型转换。

二、依赖管理踩坑:版本冲突与依赖缺失

踩坑1:Maven 依赖版本冲突,启动报 NoSuchMethodError

场景:引入第三方依赖(如 MyBatis-Plus、FastJSON)后,启动项目抛出 java.lang.NoSuchMethodErrorClassNotFoundException 异常。

原因:SpringBoot 父工程已管理了部分依赖的版本,若手动引入的第三方依赖版本与父工程管理的版本不兼容,会导致类方法缺失或类加载异常。例如,SpringBoot 2.7.x 适配 MyBatis-Plus 3.5.x,若引入 3.3.x 版本则可能出现冲突。

解决方案:1. 优先使用 SpringBoot 父工程管理的依赖版本,避免手动指定版本;2. 若需指定版本,确认该版本与当前 SpringBoot 版本兼容;3. 使用 Maven Helper 插件(IDEA 插件)分析依赖树,排查冲突的依赖,通过 <exclusions> 排除冲突的子依赖。

示例:排除冲突的依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

踩坑2:遗漏 starter 依赖,自动配置失效

场景:使用 SpringBoot 整合 Redis、MySQL 等组件时,未引入对应的 starter 依赖,导致启动后无法自动配置相关 Bean,抛出 No qualifying bean of type 'xxx' available异常。

原因:SpringBoot 的自动配置功能依赖于对应的 starter 依赖,starter 中包含了自动配置类、默认依赖和配置项。例如,整合 Redis 需引入 spring-boot-starter-data-redis,整合 MySQL 需引入 spring-boot-starter-jdbcmybatis-plus-boot-starter

解决方案:根据整合的组件,引入对应的 SpringBoot starter 依赖,避免手动引入零散的基础依赖。常用 starter 依赖可参考 SpringBoot 官方文档,确保依赖引入完整。

三、接口开发踩坑:请求参数与响应处理误区

踩坑1:@RequestBody 与 @RequestParam 混用,参数接收失败

场景:开发 POST 接口时,同时使用 @RequestBody@RequestParam 接收参数,前端请求后后端无法正确获取参数,或抛出 HttpMessageNotReadableException 异常。

原因:@RequestBody 用于接收请求体中的 JSON 数据,适用于 POST、PUT 等请求方式;@RequestParam 用于接收 URL 拼接的参数(query 参数)或表单提交的参数(form-data)。两者混用会导致请求体解析异常,尤其是当请求体为 JSON 时,无法同时解析 query 参数和请求体。

解决方案:1. 若前端传递 JSON 数据,统一使用 @RequestBody 接收,封装为实体类;2. 若需同时传递 query 参数和 JSON 数据,可分开接收(query 参数用 @RequestParam,JSON 数据用 @RequestBody),但需确保前端请求格式正确;3. 表单提交场景使用 @RequestParam@ModelAttribute,避免使用 @RequestBody

踩坑2:跨域请求失败,报 CORS 异常

场景:前后端分离项目中,前端通过 Ajax 或 Axios 请求后端接口,浏览器控制台抛出 No 'Access-Control-Allow-Origin' header is present on the requested resource 跨域异常。

原因:浏览器的同源策略限制了不同域名、端口之间的请求,后端未配置跨域支持,导致请求被拦截。

解决方案:在 SpringBoot 中配置跨域支持,常用两种方式:1. 全局配置,通过实现 WebMvcConfigurer 接口配置跨域规则;2. 局部配置,在接口方法或控制器上添加 @CrossOrigin 注解。

示例:全局跨域配置

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 允许所有接口跨域
                .allowedOriginPatterns("*") // 允许所有域名,生产环境建议指定具体域名
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方式
                .allowedHeaders("*") // 允许的请求头
                .allowCredentials(true) // 是否允许携带Cookie
                .maxAge(3600); // 预检请求的缓存时间
    }
}

四、数据库操作踩坑:事务与连接池问题

踩坑1:@Transactional 注解失效,事务未回滚

场景:在业务方法上添加@Transactional 注解后,方法执行过程中抛出异常,但数据库操作未回滚,数据出现脏数据。

原因:常见原因有:1. 注解添加在非 public 方法上(@Transactional 仅对 public 方法生效);2. 异常被 try-catch 捕获,未抛出到 Spring 事务管理器;3. 抛出的异常是非运行时异常(如 Checked Exception),而@Transactional 默认只捕获 RuntimeExceptionError;4. 方法被同类内部调用,AOP 代理失效。

解决方案:1. 确保 @Transactional 注解添加在 public 方法上;2. 若捕获异常,需在 catch 块中手动抛出 RuntimeException,或使用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 手动回滚;3. 针对非运行时异常,添加 @Transactional(rollbackFor = Exception.class) 指定回滚异常类型;4. 避免同类内部调用事务方法,可通过注入自身 Bean 或使用 AOP 代理调用。

踩坑2:数据库连接池配置不当,出现连接耗尽

场景:项目运行一段时间后,出现Could not get JDBC Connection; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available 异常,无法获取数据库连接。

原因:数据库连接池配置不合理,如最大连接数设置过小,无法满足高并发请求;或存在未关闭的数据库连接(如 ResultSet、Statement、Connection 未关闭),导致连接泄露,最终连接池耗尽。

解决方案:1. 合理配置连接池参数,根据项目并发量调整spring.datasource.hikari.maximum-pool-size(建议设置为 CPU 核心数*2+1);2. 使用 MyBatis、JPA 等框架时,确保 SQL 操作后资源自动关闭(框架已封装,避免手动操作 Connection);3. 排查代码中是否存在未关闭的数据库连接,可通过 HikariCP 监控查看连接使用情况;4. 添加连接池超时配置,避免连接长期占用。

示例:HikariCP 核心配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10 # 最大连接数
      minimum-idle: 5 # 最小空闲连接数
      connection-timeout: 30000 # 连接超时时间(ms)
      idle-timeout: 600000 # 空闲连接超时时间(ms)
      max-lifetime: 1800000 # 连接最大生命周期(ms)

五、总结与避坑技巧

SpringBoot 开发中的“坑”,大多源于对框架自动配置原理、注解使用规范、依赖管理规则的不熟悉。结合本次踩坑经历,总结几点避坑技巧:

1. 熟悉 SpringBoot 自动配置原理,明确注解的使用场景和注意事项,避免遗漏关键注解;

2. 规范依赖管理,优先使用父工程管理的版本,避免版本冲突,善用依赖分析工具排查问题;

3. 接口开发时,明确请求参数传递方式,规范跨域配置,避免参数接收和跨域异常;

4. 数据库操作中,正确使用事务注解,合理配置连接池,避免事务失效和连接耗尽;

5. 遇到问题时,优先查看启动日志和异常堆栈信息,定位问题核心,善用搜索引擎和官方文档。

开发过程中踩坑不可避免,关键是要总结经验,深入理解框架底层原理,才能从根源上避免同类问题。后续我会持续更新更多 SpringBoot 开发中的踩坑案例,欢迎大家交流补充。

(注:部分内容可能由 AI 生成)