51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

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参数校验方式一}

写一个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参数校验方式二}

同样写一个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注解说明 {#valid注解说明}

javaJSR303声明了@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 | 必须为Email,也可以通过正则表达式和flag指定自定义的email格式 |

赞(1)
未经允许不得转载:工具盒子 » SpringBoot如何优雅的处理参数校验