Spring 5.1.6中@ControllerAdvice失效问题(Kotlin实现)
嘿,我帮你梳理下问题的核心原因和解决办法:
从你的日志和代码来看,有两个关键问题导致异常处理器没生效:
1. 你处理的异常类型和实际抛出的完全不匹配
你的@ExceptionHandler标注的是javax.validation.ConstraintViolationException——这是Bean Validation(比如@NotBlank这类注解)参数校验失败时抛出的异常。但从堆栈日志里能看到,实际触发的是数据库唯一键违反,异常链是:
DataIntegrityViolationException(Spring包装后的异常) →org.hibernate.exception.ConstraintViolationException(Hibernate抛出的数据库约束异常) → 底层SQL唯一键冲突
这里的org.hibernate.exception.ConstraintViolationException和你要处理的javax.validation.ConstraintViolationException是两个不同包下的类,完全不是一回事!所以你的异常处理器根本捕获不到这个异常,自然会跳转到默认的/error页面。
2. 参数解析错误的根源
日志里的Could not resolve parameter [0]也是因为上述异常类型不匹配导致的——Spring找不到javax.validation.ConstraintViolationException的实例来注入到你的处理器方法里,所以抛出了解析错误。
方案一:直接捕获Spring包装的数据库约束异常
修改你的异常处理器,直接处理DataIntegrityViolationException,还可以根据约束名称做更精准的提示:
@ControllerAdvice class UserExceptionHandler { @ExceptionHandler(DataIntegrityViolationException::class) fun handleDataIntegrityViolation(e: DataIntegrityViolationException): ResponseEntity<*> { val rootCause = e.rootCause // 判断是否是Hibernate的数据库约束违反异常 if (rootCause is org.hibernate.exception.ConstraintViolationException) { // 可以通过约束名称判断是不是邮箱唯一键的问题 val constraintName = rootCause.constraintName return if (constraintName?.contains("MAIL_ADDRESS") == true) { ResponseEntity.status(HttpStatus.FORBIDDEN) .body("邮箱已被注册,请更换其他邮箱") } else { ResponseEntity.status(HttpStatus.BAD_REQUEST) .body("数据不符合约束,请检查参数") } } return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("服务器内部错误") } }
方案二:提前校验,避免触发数据库异常(更优雅)
在保存用户前先查询邮箱是否存在,主动抛出自定义异常,再用处理器捕获:
第一步:定义自定义异常
class DuplicateEmailException(message: String) : RuntimeException(message)
第二步:修改Controller逻辑
@RestController @RequestMapping("/api") class UserController (@Autowired private val userRepository: UserRepository) { @PostMapping("/new-user") fun createNewUser(@RequestParam mailAddress: String, @RequestParam password: String): ResponseEntity<UserEntity> { // 提前检查邮箱是否已存在 if (userRepository.existsByMailAddress(mailAddress)) { throw DuplicateEmailException("邮箱已被注册,请更换其他邮箱") } return ResponseEntity.ok(userRepository.saveAndFlush(UserEntity(mailAddress = mailAddress, password = password))) } }
第三步:给Repository添加查询方法
interface UserRepository : JpaRepository<UserEntity, Long> { fun existsByMailAddress(mailAddress: String): Boolean }
第四步:修改异常处理器捕获自定义异常
@ControllerAdvice class UserExceptionHandler { @ExceptionHandler(DuplicateEmailException::class) fun handleDuplicateEmail(e: DuplicateEmailException): ResponseEntity<*> { return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.message) } }
这种方式更友好,避免了底层数据库异常的抛出,也能给用户更精准的提示。
- 你的
@ControllerAdvice类没问题,因为所有文件在同一包下,Spring Boot主启动类会自动扫描到它 - 别加
@EnableWebMvc!Spring Boot已经默认配置好了WebMvc,加了反而可能破坏默认配置,导致其他问题
内容的提问来源于stack exchange,提问作者ciurlaro

