SpringBoot2.x之参数校验详解

在正常的业务处理中,针对外部的情况,校验参数的合法性是必须的,而在Spring MVC中有两种验证方式:Spring自带的验证框架和基于JSR实现的框架。

其中JSR(JSR303/SR-349)是一个规范文档,规定一些校验规范。Hibernate Validator就是基于JSR303规范的具体实现,提供了JSR 规范中内置约束注解的实现,同时附加了一些约束注解。

当然,用户也可以可以自定义约束注解。

依赖引入

当我们引入spring-boot-starter-web时,该starter会默认引入hibernate-validator,也就是Hibernate Validator框架。Spring Boot的参数校验正是依赖于Hibernate Validator框架来进行。

            org.springframework.boot        spring-boot-starter-web    

上述start中间接引入如下配置:

  org.springframework.boot  spring-boot-starter-validation  2.2.2.RELEASE  compile            tomcat-embed-el      org.apache.tomcat.embed      

在校验数据时,需要定义对应的数据模型(Java Bean),通过注解来指定字段校验的规则,下面具体的实例来进行演示。

实例

Controller中对订单进行参数信息进行校验。首先定义Order对象,并在其内通过注解对其属性进行约束。

@Data
public class Order {

  @NotEmpty(message = "请求流水号不能为空")
  private String requestNo;

  @Min(value = 1,message = "至少购买1件")
  @Max(value = 10,message = "最多不超过10件")
  private int amount;

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

  private String goodsName;

}

 

在Controller中的方法中使用Order作为参数,并通过添加@Valid注解开启校验。


@RestController
public class OrderController {

  @PostMapping("/buy")
  public void buy(@Valid Order order, BindingResult result) {

  

    if (result.hasErrors()) {
      result.getAllErrors().forEach((error) -> {
        System.out.println(error.getCode() + "-" + error.getDefaultMessage());
      });
    }
  }
}

其中buy方法的参数BindingResult用于存储校验结果信息,可以通过其hasErrors方法来判断是否校验通过,校验不通过时可以将错误信息打印出来或进行返回。

BindingResult必须跟在被校验参数之后,若被校验参数之后没有BindingResult对象,将会抛出BindException。

需要注意的是@Valid和BindingResult是一一对应的,如果有多个@Valid,那么每个@Valid后面都需要添加BindingResult用于接收Bean中的校验信息,顺序不能乱。

单元测试方法如下:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class Demo1ApplicationTests {
    @Autowired
    protected MockMvc mockMvc;

//     .perform() 执行一个MockMvcRequestBuilders请求。其中.get()表示发送get请求(可以使用get、post、put、delete等);.contentType()设置编码格式;.param()请求参数,可以带多个。
//     andExpect()添加 MockMvcResultMatchers验证规则,验证执行结果是否正确。
//     .andDo()添加 MockMvcResultHandlers结果处理器,这是可以用于打印结果输出。
//     .andReturn()结果还回,然后可以进行下一步的处理。
    @Test
    public void buy() throws Exception {
        mockMvc.perform(
                MockMvcRequestBuilders.post("/buy")
                        .param("requestNo", "")
                        .param("amount", "0")
                        .param("phone", "11111111111")
        ) .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();;
    }
}

执行结果打印:

请求order信息:Order(requestNo=, amount=0, phone=11111111111, goodsName=null)Pattern-手机号码格式错误NotEmpty-请求流水号不能为空Min-至少购买1件

常用校验注解

  • @Null:限制只能为null。

  • @NotNull:限制必须不为null。

  • @AssertFalse:限制必须为false。

  • @AssertTrue:限制必须为true。

  • @DecimalMax(value):限制必须为一个不大于指定值的数字。

  • @DecimalMin(value):限制必须为一个不小于指定值的数字。

  • @Digits(integer,fraction):限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction。

  • @Future:限制必须是一个将来的日期。

  • @Max(value):限制必须为一个不大于指定值的数字。

  • @Min(value):限制必须为一个不小于指定值的数字。

  • @Past:限制必须是一个过去的日期。

  • @Pattern(value):限制必须符合指定的正则表达式。

  • @Size(max,min):限制字符长度必须在min到max之间。

  • @Past:验证注解的元素值(日期类型)比当前时间早。

  • @NotEmpty:验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)。

  • @NotBlank:验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格。

  • @Email:验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式。

 

在SpringBoot的使用过程中,默认使用hibernate-validator作为参数校验的框架,但某些业务场景或校验比较复杂,通过默认提供的注解已经无法满足。此时,除了使用正则表达式来进行校验也可以使用自定义的注解。

比如,对于手机号的简单校验如下:

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

虽然能够完成工作,但是如果多出都出现类似的功能或更复杂的功能,每次都写如此多内容,显得有些臃肿。那么,我们这篇文章就来展示如何通过自定义注解来完成相同的功能。

自定义注解

我们知道hibernate validation实现JSR的标准,同时提供了一些API和扩展性的规范。要实现自定义注解,可以通过实现ConstraintValidator接口来完成。

下面看具体示例,首先定义手机号校验注解@Phone。

 /**
  * 手机号校验
  */
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {

  /**
   * 错误提示
   */
  String message() default "手机号格式错误";
  /**
   * 分组校验
   */
  Class[] groups() default {};

  Class[] payload() default {};
}

这里的@Phone注解组合了@Constraint注解,并通过其指定了真正进行校验类。

其中message为定制化的提示信息;groups主要进行将validator进行分类,不同的类group中会执行不同的validator操作;payload主要是针对bean的,使用不多。

PhoneValidator的具体实现如下:


public class PhoneValidator implements ConstraintValidator {

  @Override
  public boolean isValid(String phone, ConstraintValidatorContext constraintValidatorContext) {

    if (!StringUtils.isEmpty(phone)) {
      // 禁用默认提示信息
      constraintValidatorContext.disableDefaultConstraintViolation();
      // 设置提示语
      constraintValidatorContext.buildConstraintViolationWithTemplate("手机号格式错误").addConstraintViolation();

      String regex = "^1(3|4|5|7|8)\\d{9}$";
      return phone.matches(regex);
    }
    return true;
  }
}

 

ConstraintValidator的泛型部分,第一个值为自定义注解类型,第二个值是被注解字段的类型。

在实现的isValid方法中进行对应数据的校验,符合条件返回true,否则返回false。

完成上述定义,只需想往常一样将@Phone使用在对应的属性上便可以了。​​​​​​​

@Phone@NotBlank(message = "手机号码不能为空")private String phone;

是不是使用起来非常简约,基于注解,无侵入逻辑。

自定义执行Validator

如果某些场景不希望由系统自行触发Validator的验证逻辑,则可以直接获取Validator实例进行验证。Validator的实例化Bean已经在Spring容器中,通过@Autowired即可获得。

@Autowred
private Validator validator;

对应的单元测试示例如下:

@SpringBootTest
public class ValidatorTest {

  @Autowired
  private Validator validator;

  @Test
  void testValidator() {

    Order order = new Order();
    order.setRequestNo("");
    order.setAmount(0);
    order.setPhone("11111111111");

    Set> validate = validator.validate(order);
    if (!validate.isEmpty()) {
      validate.forEach((violation) -> {
        log.info(violation.getMessage());
      });
    }

  }
}

转载 https://mp.weixin.qq.com/s/Dc2q-eHQrViWDrPZT2_jYw

你可能感兴趣的:(java错误)