SpringBoot如何优雅的处理参数校验
引言
对于一个web项目而言后端经常需要对前端参数进行校验,传统方式常常是在controller
中使用大量if else
进行参数合法性校验,这样做的缺点显而易见,便不在赘述。
解决方案
对于以上问题,SpringBoot
项目我列举了三种处理方式:
对于前端传参的实体我们可以写这样一个类例如UserParam
,然后使用javax.validation
提供的注解进行参数条件限制
@Data
public class UserParam {
private Integer id;
@NotBlank(message="用户名不能为空")
private String username;
@Pattern(regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\\\.[a-zA-Z0-9_-]+)+$", message = "邮箱地址格式不正确")
private String email;
@Pattern(regexp="^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$", message="手机号格式不正确")
private String telephone;
@Max(value=144, message="长度必须小于144个字符")
private String description;
}
基于这样一个类,提出了两种关于aop
的方式和一种异常处理的方式
AOP参数校验方式一
写一个AOP
参数校验的类:
@Aspect
@Component
public class ValidParamAdvice {
/**
* 定义切点,切点为xyz.guqing.storycard.controller包和子包里任意方法的执行
*/
@Pointcut("execution(* xyz.guqing.storycard.controller..*(..))")
public void controllerPointCut() {
}
@Around("controllerPointCut() && args(.., bindingResult)")
public Object doAround(ProceedingJoinPoint joinPoint, BindingResult bindingResult) throws Throwable {
// 收集并返回参数校验错误信息,这里根据实际返回结果类自己封装
if(bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>(16);
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
fieldErrors.forEach(fieldError -> {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return errors.toString();
}
return joinPoint.proceed(joinPoint.getArgs());
}
}
然后在controller
中使用如下,只需要在参数UserParam
前添加@Valid
即可完成参数校验
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@PostMapping("/advice-valid")
public String exceptionAdviceValidUser(@RequestBody @Valid UserParam userParam) {
return userParam.toString();
}
}
AOP参数校验方式二
同样写一个aop
参数处理类
@Aspect
@Component
public class ValidParamAdvice {
/**
* 定义切点,切点为xyz.guqing.storycard.controller包和子包里任意方法的执行
*/
@Pointcut("execution(* xyz.guqing.storycard.controller..*(..))")
public void controllerPointCut() {
}
@Around("controllerPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
BindingResult bindingResult = null;
// 使用遍历参数的方式找到BindingResult
for(Object arg:joinPoint.getArgs()){
if(arg instanceof BindingResult){
bindingResult = (BindingResult) arg;
}
}
if(bindingResult != null && bindingResult.hasErrors()){
Map<String, String> errors = new HashMap<>(16);
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
fieldErrors.forEach(fieldError -> {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return errors.toString();
}
return joinPoint.proceed();
}
}
在controller
中使用
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@PostMapping("/aop-valid")
public String aopValidUser(@RequestBody @Valid UserParam userParam, BindingResult result) {
return userParam.toString();
}
}
如上,不同点在于不仅需要在UserParam
参数前写@Valid
,还需要多添加一个参数BindingResult result
才可以
全局异常处理参数校验
写这样一个异常处理类
@RestControllerAdvice
@Slf4j
public class ValidExceptionAdvice {
/**
* 当使用@Valid不带@RequestBody request参数时:
* 对象验证失败,验证将引发BindException而不是MethodArgumentNotValidException
* @param e 参数绑定异常
* @return 返回参数校验失败的错误信息
*/
@ExceptionHandler(BindException.class)
public Object validExceptionHandler(BindException e){
// 将错误的参数的详细信息封装到统一的返回实体
return validParam(e.getBindingResult());
}
/**
* 使用@Valid并且带有@RequestBody request参数时
* 参数教研失败将抛出MethodArgumentNotValidException异常,由此方法捕获处理
* @param e 方法参数校验失败的异常
* @return 返回校验失败错误信息
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e){
return validParam(e.getBindingResult());
}
private Object validParam(BindingResult bindResult) {
List<FieldError> fieldErrors = bindResult.getFieldErrors();
Map<String, String> map = new HashMap<>(16);
fieldErrors.forEach(fieldError -> {
map.put(fieldError.getField(), fieldError.getDefaultMessage());
});
return map;
}
}
在controller
中使用
@RestController
@CrossOrigin
@RequestMapping("/user")
public class UserController {
@PostMapping("/advice-valid")
public String exceptionAdviceValidUser(@RequestBody @Valid UserParam userParam) {
return userParam.toString();
}
}
只需要在需要进行参数校验的参数UserParam
前添加@Valid
即可,当校验不通过时会被ValidExceptionAdvice
类拦截处理
@Valid注解说明
java
的JSR303
声明了@Valid
这类接口,而Hibernate-validator
对其进行了实现,因此具体注解使用参考Hibernate Validator,下面列举一些常见注解的使用说明:
注解 | 备注 |
---|---|
@Null | 只能为null |
@NotNull | 必须不为null |
@Max(value) | 必须为一个不大于 value 的数字 |
@Min(value) | 必须为一个不小于 value 的数字 |
@AssertFalse | 必须为false |
@AssertTrue | 必须为true |
@DecimalMax(value) | 必须为一个小于等于 value 的数字 |
@DecimalMin(value) | 必须为一个大于等于 value 的数字 |
@Digits(integer,fraction) | 必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction |
@Past | 必须是 日期 ,且小于当前日期 |
@Future | 必须是 日期 ,且为将来的日期 |
@Size(max,min) | 字符长度必须在min到max之间 |
@Pattern(regex=,flag=) | 必须符合指定的正则表达式 |
@NotEmpty | 必须不为null且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank | 必须不为空(不为null、去除首位空格后长度不为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
必须为Email,也可以通过正则表达式和flag指定自定义的email格式 |