在 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.NoSuchMethodError 或 ClassNotFoundException 异常。
原因: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-jdbc 或 mybatis-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 默认只捕获 RuntimeException 和 Error;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 生成)