本文介绍在Spring Boot中实现对请求的数据进行校验。数据校验常用到概念:
JSR303/JSR-349: JSR303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349是其的升级版本,添加了一些新特性。hibernate validation:hibernate validation是对这个规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等spring validation:spring validation对hibernate validation进行了二次封装,在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中本文主要包括如下内容:
演示spring boot validation + 异常捕获机制实现数据自动校验功能自定义校验注解,并演示这个用法功能:向服务发送请求,这个请求带上参数,服务需要对参数进行校验。
封装返回处理结果,如果code=200,则表示添加成功,code=400,则表示输入的数据异常
public class Result { private int code; private String message; // set/get方法略 … }客户端的请求封装到这个dto中。使用校验注解注解此类的成员属性。这些注解的功能看名称就可以看出来。其中 @PhoneValidation就我们自定义的注解,这个后面会说明。每个注解里有个属性message,如果我们不想使用系统默认提供的报错信息,我们可以修改这个值
public class CustomerDto { @Size(min=2, max=30) private String name; // 自定义错误信息 @NotEmpty(message = "自定义错误信息,Email不能为空") @Email private String email; @NotNull @Min(18) @Max(100) private Integer age; @NotNull private Gender gender; @DateTimeFormat(pattern="MM/dd/yyyy") @NotNull @Past private Date birthday; // 自定义规则注解 @PhoneValidation private String phone; public enum Gender { MALE, FEMALE } // set/get方法略 … }如果数据校验不通过,则Spring boot会抛出BindException异常,我们可以捕获这个异常并使用Result封装返回结果。通过@RestControllerAdvice定义异常捕获类
@RestControllerAdvice public class BindExceptionHanlder { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(BindException.class) public Result handleBindException(BindException ex) { // ex.getFieldError():随机返回一个对象属性的异常信息。如果要一次性返回所有对象属性异常信息,则调用ex.getAllErrors() FieldError fieldError = ex.getFieldError(); StringBuilder sb = new StringBuilder(); sb.append(fieldError.getField()).append("=[").append(fieldError.getRejectedValue()).append("]") .append(fieldError.getDefaultMessage()); // 生成返回结果 Result errorResult = new Result(); errorResult.setCode(400); errorResult.setMessage(sb.toString()); return errorResult; } }提供Control层的对外接口
@RestController public class ValidationCtrl { private static final Logger logger = LoggerFactory .getLogger(ValidationCtrl.class); @RequestMapping(value = "/validation/save", method = RequestMethod.GET) public Result saveCustomerPage(@Validated CustomerDto model) { logger.info("Good" + model.getBirthday()); Result okResult = new Result(); okResult.setCode(200); okResult.setMessage(JSONObject.toJSON(model).toString()); return okResult; } }功能:自定义校验注解,对输入手机进行合法性检查。并演示这个用法
定义自己的校验注解 头注解@Constraint属性validatedBy 指定真正执行校验的类PhoneValidationValidator
@Documented // 指定真正实现校验规则的类 @Constraint(validatedBy = PhoneValidationValidator.class) @Target( { ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface PhoneValidation { String message() default "不是正确的手机号码"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented @interface List { PhoneValidation[] value(); } }真正执行校验的类,对输入的手机执行校验
public class PhoneValidationValidator implements ConstraintValidator<PhoneValidation, String> { private static final Pattern PHONE_PATTERN = Pattern.compile( "^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\\d{8}$" ); @Override public void initialize(PhoneValidation constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if ( value == null || value.length() == 0 ) { return true; } Matcher m = PHONE_PATTERN.matcher(value); return m.matches(); } }所有的详细代码见github代码,请尽量使用tag v0.18,不要使用master,因为master一直在变,不能保证文章中代码和github上的代码一直相同
