引言
在开发基于Spring MVC的应用程序时,数据校验是一个重要的环节。
随着项目的扩大,如果是像上一节在Controller中通过BindingResult获取校验结果并封装,需要在不同的业务场景接口中反复实现数据校验逻辑,这不仅增加了代码的冗余性,同时也提高了维护成本。
为了解决这些问题,可以利用Spring MVC提供的ControllerAdvice
功能来创建一个统一的数据校验异常处理机制。
本文将基于ControllerAdvice
机制,使得所有控制器的数据校验异常都能被集中处理,并且能够更优雅地返回错误信息。
一,创建统一的异常处理类
ControllerAdvice简介
ControllerAdvice
是 Spring Framework 中的一个注解,用于创建一个全局的异常处理类,它可以捕获并处理 Spring MVC 控制器中抛出的异常。
这个注解提供了一种集中处理控制器中可能出现的各种异常的方法,从而避免在每个控制器方法中重复编写异常处理逻辑。
抽取异常处理类
-
创建类
在项目中创建一个新的类,将它命名为GulimallExceptionControllerAdvice
,并将其放在专门用于存放异常处理类的包中。 -
添加注解
使用@RestControllerAdvice
注解标记这个类,表明它是一个全局异常处理类。同时,这个注解还带有@Component
注解,意味着它会被Spring容器管理。 -
指定处理范围
可以使用basePackages
属性来指定这个异常处理器应该处理哪些控制器的异常。
实现异常处理方法
-
定义方法
创建一个方法来处理数据校验异常,命名为handleDataValidationException
。 -
使用
@ExceptionHandler
注解
使用@ExceptionHandler
注解来指明这个方法将处理哪种类型的异常。在这里,可以指定它处理MethodArgumentNotValidException
。 -
访问异常信息
在方法参数中声明一个MethodArgumentNotValidException
类型的参数,这样可以在方法体内访问到具体的校验错误信息。 -
构建错误响应
根据异常中的信息构建一个错误响应对象,并返回给客户端。
GulimallExceptionControllerAdvice
类完整代码
package com.atguigu.gulimall.product.exception;
import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
@Slf4j
public class GulimallExceptionControllerAdvice {
/**
* 参数非法(效验参数)异常 MethodArgumentNotValidException
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public R handleValidException(MethodArgumentNotValidException e) {
log.error("数据效验出现问题{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> {
errMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error()
.put("data",errMap);
}
}
标准化错误状态码
随着业务逻辑的增加和代码量的增长,使用统一的错误状态码变得尤为重要。
这些状态码可以帮助前端开发者快速识别问题所在,并采取相应的措施。为了实现这一点,我们定义了一套规范化的状态码体系,这些状态码由五位数字组成,其中前两位代表业务场景,而后三位则标识具体的错误类型。
以下是状态码定义的具体步骤:
-
确定业务场景编码:
- 前两位数字表示不同的业务场景。例如,“10”代表通用错误,如数据校验错误或服务器内部异常;“12”可能代表订单模块;“13”代表购物车模块;“14”代表物流模块等。
-
定义错误代码:
- 后三位数字表示特定的错误类型。例如,“001”表示系统未知异常,“002”表示参数校验错误等。
-
枚举定义:
- 为了方便管理和使用这些状态码,我们将其封装在一个枚举类中。这个枚举类通常命名为
BaseCode
并存放在exception
包中。 - 枚举类中的每个条目对应一个具体的错误状态码及其描述信息。
- 例如,定义一个名为
VALID_EXCEPTION
的枚举项,其状态码为 “10000001”,描述信息为 “参数格式校验失败”。
- 为了方便管理和使用这些状态码,我们将其封装在一个枚举类中。这个枚举类通常命名为
-
枚举构造与访问:
- 枚举类中的每个条目通过一个私有构造函数接收状态码和描述信息作为参数。
- 构造函数设置状态码和描述信息,并提供
getCode()
和getMessage()
方法供外部访问这些信息。
-
使用枚举处理异常:
- 在
ControllerAdvice
类中处理异常时,可以直接引用这些枚举项。 - 例如,当数据校验失败时,可以返回
BaseCode.VALID_EXCEPTION
的状态码和描述信息。
- 在
-
扩展与维护:
- 随着项目的演进,可以随时向枚举中添加新的状态码。
- 这样,当出现错误时,可以通过枚举直接获取到错误的状态码和描述信息,简化了错误处理流程。
综上所述,定义统一的错误状态码有助于提高代码的可读性和可维护性,同时也使得前端开发者能够更高效地理解后端返回的错误信息。
例如,在数据校验失败的情况下,返回的状态码为 “10001”,提示信息为 “参数格式校验失败”,这使得前端能够明确地了解问题所在,并作出相应的处理。
在common模块的exception包下创建一个枚举类BizCodeEnum。
测试与验证
- 移除显式校验
测试前,要确定是否已经将上一节在Controller中的异常处理代码注释或者删除。
这样的话,数据校验失败时就会抛出异常。
-
发送测试请求
发送一个包含无效数据的HTTP请求到相应的端点。 -
验证响应
验证响应中包含了正确的错误信息和HTTP状态码。
后台也会打印错误信息。
结论
通过使用@ControllerAdvice
和@ExceptionHandler
,我们可以创建一个统一的异常处理机制,有效地减少了代码冗余,并提高了系统的可维护性和可读性。这种方式适用于任何需要集中处理异常的Spring MVC应用。