Spring下的数据校验体系

由于项目中经常需要对数据进行校验,我们下面将对spring提供的数据校验机制进行详解。

在论述spring提供的校验机制前,首先简述 JSR303/JSR-349,hibernate validation,spring validation 之间的关系。JSR303 是一项标准,JSR-349 是其的升级版本,添加了一些新特性,他们规定一些校验规范即校验注解,如 @Null,@NotNull,@Pattern,他们位于 javax.validation.constraints 包下,只提供规范不提供实现。而 hibernate validation 是对这个规范的实践(不要将 hibernate 和数据库 orm 框架联系在一起),他提供了相应的实现,并增加了一些其他校验注解,如 @Email,@Length,@Range 等等,他们位于 org.hibernate.validator.constraints 包下。而万能的 spring 为了给开发者提供便捷,对 hibernate validation 进行了二次封装,显示校验 validated bean 时,你可以使用 spring validation 或者 hibernate validation,而 spring validation 另一个特性,便是其在 springmvc 模块中添加了自动校验,并将校验信息封装进了特定的类中。这无疑便捷了我们的 web 开发。本文主要介绍在 springmvc 中自动校验的机制。

由于hibernate validation 是java校验标准的最好实践,当我们在做spring-web项目时,spring就已经自动引入了相关包,所以我们不需要引入任何的jar包了。为方便大家的理解,我们将从以下几个方面给大家讲解。

1、基本注解

@Null   被注释的元素必须为 null    
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式


@NotBlank(message =)   验证字符串非 null,且长度必须大于 0    
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

我们在项目中可以灵活应用上述注解完成基本的功能。具体功能演示如下:

public class Foo {    
    @NotBlank
    private String name;

    @Min(18)
    private Integer age;

    @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
    @NotBlank(message = "手机号码不能为空")
    private String phone;

    @Email(message = "邮箱格式错误")
    private String email;
    
    //... getter setter

}

@Controller
public class FooController {

@RequestMapping("/foo")
public String foo(@Validated Foo foo <1>, BindingResult bindingResult <2>) {
if(bindingResult.hasErrors()){
for (FieldError fieldError : bindingResult.getFieldErrors()) {
//...
}
return "fail";
}
return "success";
}

}

 

验证完毕后的错误信息将会放到bindingResult中,可以通过bindingResult提取到错误信息。

2、自定义校验

由于基本的校验注解满足不了我们日常工作的需要,可能需要自己编写校验逻辑。我们首先编写注解,然后编写校验实现工具类。

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CannotHaveBlankValidator.class})<1>
public @interface CannotHaveBlank {

    // 默认错误消息
    String message() default "不能包含空格";

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};

    // 指定多个时使用
    @Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        CannotHaveBlank[] value();
    }
}

// 校验逻辑实现工具类

public class CannotHaveBlankValidator implements <1> ConstraintValidator<CannotHaveBlank, String> {

    @Override
    public void initialize(CannotHaveBlank constraintAnnotation) {
    }
    
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context <2>) {
        //null 时不进行校验
        if (value != null && value.contains(" ")) {
            <3>
            // 获取默认提示信息
            String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate();
            System.out.println("default message :" + defaultConstraintMessageTemplate);
            // 禁用默认提示信息
            context.disableDefaultConstraintViolation();
            // 设置提示语
            context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation();
            return false;
        }
        return true;
    }
}

3、异常捕获

在第一部分中可以看到校验逻辑完毕后的错误信息捕获都是在controller类中,如果代码过多那么每个类中均要实现上面的捕获逻辑,则相当的麻烦;

并且代码基本类似,重复性太厉害了。所以,我们结合spring的机制,可以自定义异常的捕获机制。代码展示如下:


package com.shinho.plrs.front.cores.advice;


import com.shinho.plrs.front.cores.pojo.Response;
import com.shinho.plrs.front.infrastructure.exception.CommonExcepEnums;
import com.shinho.plrs.front.infrastructure.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

/**
* @description:系统全局的异常处理机制
* @date: 2019/1/16 08:54
*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


/**
* 自定义异常处理机制
*
* @param
* @return
*/
@ExceptionHandler(value = CommonException.class)
@ResponseBody
public Response myErrorHandler(HttpServletRequest request, CommonException e) {
log.error("[{}]接口异常:{}", request.getRequestURI(), e);
return Response.error(e.getErrCode(), e.getErrMsg());
}

/**
* 系统异常处理
*
* @param
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Response errorHandler(HttpServletRequest request, Exception ex) {
log.error("[{}]系统异常:{}", request.getRequestURI(), ex);
Throwable error = ex;
while (error != null && !(error instanceof CommonException)) {
error = error.getCause();
}
if (error instanceof CommonException && error != null) {
return Response.error((CommonException) error);
}
return Response.error(ex.getMessage());
}


/**
* 处理参数校验异常
*
* @param ex
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public Response handleBindException(HttpServletRequest request, BindException ex) {
log.error("[{}]参数校验异常:{}", request.getRequestURI(), ex);
return Response.error(getErrorMessage(ex));
}

/**
* 处理参数校验异常
*
* @param ex
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Response handleBindException(HttpServletRequest request, MethodArgumentNotValidException ex) {
log.error("[{}]参数校验异常:{}", request.getRequestURI(), ex);
return Response.error(getErrorMessage(ex));
}

/**
* 获取验证失败的异常信息
*
* @param ex
* @return
*/
private String getErrorMessage(Exception ex) {
BindingResult result = null;
StringBuilder stringBuilder = new StringBuilder();
if (ex instanceof BindException) {
result = ((BindException) ex).getBindingResult();
} else if (ex instanceof MethodArgumentNotValidException) {
result = ((MethodArgumentNotValidException) ex).getBindingResult();
}
result.getFieldErrors().stream().forEach(
(r) -> {
stringBuilder.append(r.getField() + ":" + r.getDefaultMessage() + ";");
}
);
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
return stringBuilder.toString();
}

/**
* 用来处理bean validation异常
*
* @param ex
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public Response resolveConstraintViolationException(HttpServletRequest request, ConstraintViolationException ex) {
log.error("[{}]参数校验异常:{}", request.getRequestURI(), ex);
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
StringBuilder msgBuilder = new StringBuilder();
for (ConstraintViolation constraintViolation : constraintViolations) {
msgBuilder.append(constraintViolation.getMessage()).append(",");
}
String message = null;
if (msgBuilder.length() > 0) {
msgBuilder.deleteCharAt(msgBuilder.length() - 1);
message = msgBuilder.toString();
}
return Response.error(CommonExcepEnums.ERROR);
}
}

 

  如果我们用form的形式传递参数的话,如果校验不合格,则spring自身会抛出:BindException异常;如果我们json传递参数信息的话,则会抛出MethodArgumentNotValidException

 

posted @ 2020-05-16 16:33  一家人  阅读(637)  评论(0编辑  收藏  举报