You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:00:38