@ControllerAdvice与校验异常处理的区别、作用及使用方法咨询
@ControllerAdvice与校验异常处理:区别、作用及工作原理解析
问题背景
我在项目中用校验机制处理异常,了解到@ControllerAdvice也能做异常处理,但搞不懂它的作用、和校验异常处理的差异,以及怎么用,希望有人讲下它的工作原理。
校验机制代码示例:
@RestController @RequestMapping("/api") @Validated public class UserController { @Autowired UserRepository userrepo; @PostMapping(value="/users") ResponseEntity<?> create( @Valid @RequestBody User user) { User addeduser = userrepo.save(user); URI location = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}") .buildAndExpand(addeduser.getId()) .toUri(); return ResponseEntity.created(location).build(); } }
@ControllerAdvice代码示例:
@ControllerAdvice public class GlobalResponseException { @ExceptionHandler(MyException.class) public void handleMyException() {} }
一、@ControllerAdvice的核心作用
简单说,@ControllerAdvice是Spring MVC提供的全局增强器,针对异常处理场景,它的核心价值是:
- 统一捕获全应用内抛出的指定异常,不用在每个Controller里重复写异常处理逻辑
- 可以自定义所有异常的响应格式,让前后端交互的错误信息更规范
- 除了异常处理,还能实现全局数据绑定、全局请求参数预处理(不过你主要关注异常,这俩就不多提)
二、和校验异常处理的核心差异
你用的@Valid/@Validated属于JSR-380校验规范,触发校验后会抛出MethodArgumentNotValidException(请求体校验)或ConstraintViolationException(路径/请求参数校验)这类异常。它和@ControllerAdvice的差异主要在这几点:
1. 作用范围
- 原生校验异常处理:如果不额外配置,要么依赖Spring默认的错误响应,要么得在单个Controller里写
@ExceptionHandler处理,只能覆盖当前Controller的异常 @ControllerAdvice:全局生效,所有Controller抛出的匹配异常都会被捕获,一次配置全应用复用
2. 处理的异常类型
- 校验异常处理:只能处理和校验相关的特定异常
@ControllerAdvice:可以处理任意类型的异常——自定义业务异常(比如你示例里的MyException)、系统异常、第三方服务调用异常等,覆盖场景更广
3. 代码复用性
- 原生校验处理:每个需要校验的Controller都要写重复的异常处理代码,冗余度高
@ControllerAdvice:只需要写一次异常处理逻辑,全应用所有Controller都能复用,代码更简洁易维护
三、@ControllerAdvice的工作原理
Spring容器启动时,会自动扫描并注册带有@ControllerAdvice的类,这个类相当于一个全局的异常拦截器,流程是:
- 当Controller方法抛出异常时,Spring MVC的
DispatcherServlet会遍历所有注册的@ControllerAdvice类 - 匹配每个类里的
@ExceptionHandler方法,根据抛出的异常类型找到最匹配的处理方法(比如子类异常优先匹配对应的方法,父类异常兜底) - 执行匹配到的处理方法,返回你自定义的响应(比如结构化的错误JSON、跳转错误页面等)
四、结合校验异常的实战用法
针对你给出的校验代码,我们可以用@ControllerAdvice统一处理校验异常,返回更友好的错误信息:
// 用@RestControllerAdvice更适合REST接口,默认返回JSON,不用每个方法加@ResponseBody @RestControllerAdvice public class GlobalExceptionHandler { // 处理请求体参数校验异常 @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) { Map<String, String> errorMap = new HashMap<>(); // 遍历所有校验错误,提取字段名和错误信息 ex.getBindingResult().getAllErrors().forEach(error -> { String fieldName = ((FieldError) error).getField(); String errorMsg = error.getDefaultMessage(); errorMap.put(fieldName, errorMsg); }); // 返回400错误和结构化错误信息 return ResponseEntity.badRequest().body(errorMap); } // 处理自定义业务异常 @ExceptionHandler(MyException.class) public ResponseEntity<String> handleCustomException(MyException ex) { // 返回500错误和自定义异常信息 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage()); } }
这样,当你调用/api/users接口,请求体的User对象校验不通过时,会返回类似这样的JSON:
{ "username": "用户名不能为空", "password": "密码长度不能少于6位" }
比Spring默认的错误响应清晰得多,前端也更容易解析。
内容的提问来源于stack exchange,提问作者anderson
相关产品推荐
相关产品推荐

