参数校验- bean validation

Bean Validation 2.0 关注点

  • 使用Bean Validation的最低Java版本为Java 8
  • 支持容器的校验,通过TYPE_USE类型的注解实现对容器内容的约束:List<@Email String>
  • 支持日期/时间的校验,@Past和@Future
  • 拓展元素数据:@Email,@NotEmpty,@NotBlank,@Positive, @PositiveOrZero,@Negative,@NegativeOrZero,@PastOrPresent和@FutureOrPresent

常见的元数据

meta-datacommentversion
@Null对象,为空Bean Validation 1.0
@NotNull对象,不为空Bean Validation 1.0
@AssertTrue布尔,为TrueBean Validation 1.0
@AssertFalse布尔,为FalseBean Validation 1.0
@Min(value)数字,最小为valueBean Validation 1.0
@Max(value)数字,最大为valueBean Validation 1.0
@DecimalMin(value)数字,最小为valueBean Validation 1.0
@DecimalMax(value)数字,最大为valueBean Validation 1.0
@Size(max, min)min<=value<=maxBean Validation 1.0
@Digits (integer, fraction)数字,某个范围内Bean Validation 1.0
@Past日期,过去的日期Bean Validation 1.0
@Future日期,将来的日期Bean Validation 1.0
@Pattern(value)字符串,正则校验Bean Validation 1.0
@Email字符串,邮箱类型Bean Validation 2.0
@NotEmptyCharSequence、Collection、Map、Array,不为空Bean Validation 2.0
@NotBlank字符串,不为空字符串Bean Validation 2.0
@Positive数字,正数Bean Validation 2.0
@PositiveOrZero数字,正数或0Bean Validation 2.0
@Negative数字,负数Bean Validation 2.0
@NegativeOrZero数字,负数或0Bean Validation 2.0
@PastOrPresent过去或者现在Bean Validation 2.0
@FutureOrPresent将来或者现在Bean Validation 2.0

理清关系

JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解

Bean Validation是Java定义的一套基于注解/xml的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本

Bean Validation 2.0的唯一实现为Hibernate Validator

Hibernate validator 在JSR303的基础上对校验注解进行了扩展

Hibernate Validation 是常用的针对Bean Validation API的事实上标准,并在Bean Validation 的API基础上,进行了扩展,以覆盖更多的场景。

JSR和Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数和返回值校验

Spring Validation 则在整合了Hibernate Validation 的基础上,以Spring的方式,支持Spring应用的输入输出校验,比如MVC入参校验,方法级校验等等。

pom 配置
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>2.0.1.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>6.0.14.Final</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish</groupId>
			<artifactId>javax.el</artifactId>
			<version>3.0.3</version>
		</dependency>

定义一个校验器

HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class)
        // .providerResolver( ... ) // 因为制定了Provider,这个参数就可选了
        .configure()
        .failFast(false);
ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
分组校验
public interface Update {
}

public interface Create {
}

public interface Get {
}

注:未提供组的校验注解默认为Default.class

Person 对象

@Data
public class Person {
    private long id;
    /**
     * 添加groups 属性,说明只在特定的验证规则里面起作用,不加则表示在使用Deafault规则时起作用
     */
    @NotNull(groups = {Create.class, Update.class}, message = "添加、修改用户时名字不能为空", payload = ValidateErrorLevel.Info.class)
    private String name;

    @NotNull(groups = {Create.class}, message = "添加用户时地址不能为空")
    private String address;

    @Range(min = 0, max = 150, groups = {Create.class, Update.class}, message = "姓名不能低于0岁不能高于150 岁")
    private int age;
  
    private String birth;
}

Employee 对象


@Data
public static class Employee {
    @NotNull(groups = {Update.class})
    private String uuid;

    @NotBlank(message = "员工姓名不能为空", groups = {Create.class})
    private String name;

    @Pattern(regexp = "1[0-9]{10}")
    private String number;

    @NotEmpty
    private List<@Email String> emails;

    @Valid // family中每一个Person对象都进行完整校验
    @NotEmpty
    private List<Person> family;

    @Valid // employee对象也会被作为一个DTO完整校验
    private Employee superior;
}

自定义注解

Spring 校验

Spring Validation 则在整合了Hibernate Validation 的基础上,以Spring的方式,支持Spring应用的输入输出校验,比如MVC入参校验,方法级校验等等。

Spring中的校验有两种场景,一种是MVC中的controller层校验,一种是添加@Validated的bean的校验,上面提到的例子其实是两种场景的共用的情况。

MVC中的校验比较简单,在Controller的方法入参或者出参添加@Valid或者@Validated注解,即可对标记的对象进行校验。

国际化配置
方法校验

JSR和Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验,实现如下:

实例化MethodValidationPostProcessor

@Configuration
public class ValidationConfig {
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}

控制器校验

@RestController
@Validated({Create.class, Update.class, Default.class})
public class EmployeeController {

  	@RequestMapping(value = "/employee", method = RequestMethod.GET)
  	public @NotNull(groups = Get.class) Employee getEmployee(@Length(min = 10) @RequestParam String name) {
  	}
  
    /**
     * Employee
     * 此处启用PersonAddView 这个验证规则
     * 备注:此处@Validated(Create.class) 表示使用 Create 这套校验规则,若使用 @Valid、@Validated、@Validated(Default.class) 都则表示使用默认校验规则,
     * 若两个规则同时加上去,则只有第一套起作用
     */
    @RequestMapping(value = "/employee", method = RequestMethod.POST)
    public void addEmployee(@RequestBody @Validated({Create.class}) List<Employee> employee) {
    }

    //方法上的分组覆盖类的分组
    @Validated({Default.class})
    public Object doSomething(@NotNull(groups = {Insert.class}) Object arg) {
        // do something
        return null;
    }
  
    /**
     * 修改 Employee 对象
     * 此处启用 Update 这个验证规则
     */
    @RequestMapping(value = "/employee", method = RequestMethod.PUT)
    public void modifyEmployee(@RequestBody @Validated(value = {Update.class}) Employee employee) {
    }
  
    @RequestMapping(value = "/employee", method = RequestMethod.PUT)
    public ResponseEntity<?> test3(@Validated List<Employee> employee, BindingResult result, @Validated List<Employee> employee2, BindingResult result2) {

        if (result.hasErrors()) {
            for (FieldError fieldError : result.getFieldErrors()) {
                //...
            }
            return ResponseEntity.badRequest().body("fail");
        }
    }
  

  
}

MVC 的校验中@Valid@Validated是可以互换的,行为基本一致。test1中没有将校验的结果放到BindingResult中,则controller校验未通过时,会直接扔出异常,如没有自动捕获,则请求会返回BadRequest:400

一般建议用 @Valid,统一异常处理。

import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
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.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理所有校验失败的异常(MethodArgumentNotValidException异常)
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    // 设置响应状态码为400
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result handleBindGetException(MethodArgumentNotValidException ex) {

        Map<String, Object> body = new LinkedHashMap<String, Object>();
        body.put("timestamp", new Date());

        // 获取所有异常
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());
        body.put("errors", errors);
        return new Result(false, 20001, "提交的数据校验失败", body);
    }
  
     /**
     * 处理所有参数校验时抛出的异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = ValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result handleBindException(ValidationException ex) {

        Map<String, Object> body = new LinkedHashMap<String, Object>();
        body.put("timestamp", new Date());

        // 获取所有异常
        List<String> errors = new LinkedList<String>();
        if(ex instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) ex;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                errors.add(item.getMessage());
            }
        }
        body.put("errors", errors);
        return new Result(true, 20001, "提交的参数校验失败", body);
    }
  
      @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BindException.class)
    public Result bindExceptionHandler(final BindException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return new Result(500, message);
    }


    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handler(final MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        return new Result(500, message);
    }

    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(ConstraintViolationException.class)
    public Result handler(final ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
        return new Result(500, message);
    }
}

备注:Service 层也可以校验,一般在接口校验。方法与 Controller 层一样。

参考

https://www.jianshu.com/p/21c1c463e5bf

https://www.jianshu.com/p/7726c47c6644

https://www.jianshu.com/p/26f4b0e837a0

JSR 380: Bean Validation 2.0

Bean Validation

国际化 https://blog.csdn.net/choimroc/article/details/101880225、https://www.jianshu.com/p/46eda1f96abe

附录

注:

@Valid (javax.validation): 是Bean Validation 中的标准注解,表示对需要校验的 【字段/方法/入参】 进行校验标记

@Validated(org.springframework.validation.annotation):是Spring对@Valid扩展后的变体,支持分组校验。

Validated

关于@Validated注解的功能,官方注释里面已经写的很清楚了,我这里简单翻译下:

  1. JSR-303的变种@Valid,支持验证组规范。支持基于Spring的JSR-303,但不支持JSR-303的特殊扩展。
  2. 可以用于例如Spring MVC处理程序方法参数。通过{@linkorg.springframework.validation.SmartValidator}支持组验证。
  3. 支持方法级的验证。在方法级别上添加此注解,会覆盖类上的组信息。但是方法上的注释不会作为切入点,要想方法上的注解生效,类上也必须添加注解。
  4. 支持元注解,可以添加在自定义注解上,组装为新的注解

通过官方的注释,已经能够明白这个注解的大部分功能了。

@Validated加在类上,Spring会将标注的类包装为切面,从而让类中的方法调用时,支持Java的校验,所以当使用@Validated时,不仅可以用于Controller上,其他所有的Spring的bean也都可以使用。

为何自己在使用的时候从来都没有导入过EL相关Jar包,也能正常数据校验呢?

答:那是因为绝大多数情况下你使用@Valid是使用在Spring MVC上,它是不依赖于EL方式的

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值