Spring Boot 如何优雅进行参数验证

321 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

前言

SpringBoot实际项目中, 为了保证接口的健壮性和可用性接口参数验证是非常必须的,新来实习员工完成一个注册用户功能相关代码如下:

图片.png

这段代码业务逻辑实现没有任何问题,但是代码过于臃肿,我们能否采用优雅的方式进行参数验证呢?

Spring Validation

简介

Java API规范 (JSR303) 定义了Bean校验的标准validation-api,但没有提供具体实现。 hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。 Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。

集成步骤

引入jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

注意:Spring Boot2.2之前版本需要引入spring-boot-starter-validation,而2.2之后的版本无需引入,引文spring-boot-starter-web中已经添加了此引用。

编写参数验证规则

我们需要在实体类中,定义验证规则

public class User implements Serializable
{
    private static final long serialVersionUID = -6160884495447867892L;

    @NotBlank(message = "名字必填")
    @Length(min = 1, max = 20, message = "用户名长度需要在20个字以内")
    private String name;
    
    @Email(message = "请填写正确的邮箱地址")
    @Pattern(regexp = "^([a-zA-Z]|[0-9])(\\w|\\-)+@[a-zA-Z0-9]+\\.([a-zA-Z]{2,4})$",message = "邮箱格式不正确")
    @Length(min = 5, max = 50, message = "邮箱长度需要在50个字符以内")
    private String email;

    @NotEmpty(message = "could not be empty")
    @Pattern(regexp = "^(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9]|X)$", message = "invalid ID")
    private String cardNo;

    @NotNull
    @Range(min = 0, max = 1, message = "sex should be 0-1")
    private Integer sex;
    
    @NotEmpty(message = "telphone could not be empty")
    @TelephoneNumber(message= "invalid telphone invalid")
    private String telphone;
    
    //省略get、set方法
   }

业务层添加验证

   //接口定义
   User addUser(@Valid User user);
   
   //实现类
   
  @Service
  @Validated
  @Transactional(rollbackFor=Exception.class)
  public class TUserServiceImpl implements TUserService
  {
    public User addUser(@Valid User user)
    {
        return user;
    }
  }
  
  //controller调用
  
   @Autowired
    private TUserService userService;
    
    @RequestMapping("/addUser")
    public User addUser(@RequestBody User user)
    {
      return userService.addUser(user);
    }
   

注意: Validation用于Service验证时,我们需要在定义接口时添加@Valid注解,否则测试时会报如下错误:

HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method TUserServiceImpl#addUser(User) redefines the configuration of TUserService#addUser(User).

测试验证

POST请求新增用户接口:

图片.png

统一接口返回和全局异常处理,可以参考:juejin.cn/post/708603…

错误信息如果需要实现国际化,可以参考juejin.cn/post/708646…

@Validate和@Valid什么区别?

上述的例子中分别用了@validate和@Valid两个注解,那么他们有什么区别呢?

  • @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制

  • @Valid 可以用在方法、构造函数、方法参数和成员属(字段)上,而@Validated 只能用在类型、方法和方法参数上,不能用在成员属性(字段)上。

  • @Valid 可以作用于嵌套的属性上,而@Validated不能作用于嵌套类属性上。

public class User implements Serializable {

    @Valid // 这里只能用@Valid
    private Address address;

}

Java的JSR-349规范提供了常用的验证规则

图片.png

Spring Validation 新增验证规则

图片.png

自定义Validation

虽然Spring Boot Validation提供了很多默认的验证规则,但是有些特殊的验证还是无法满足,所以我们需要自定义Validation 规则.

定义验证注解

@Target({ ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {TelephoneValidator.class}) // 指定校验器
public @interface Telephone
{
   String message() default "Invalid telephone number";
   Class<?>[] groups() default {};
   Class<? extends Payload>[] payload() default {};
}

定义验证器

public class TelephoneValidator implements ConstraintValidator<Telephone , String>
{
    private Logger logger =LoggerFactory.getLogger(Logger.class);
    
    private static final String REGEX_TELEPHONE = "0\\d{2,3}[-]?\\d{7,8}|0\\d{2,3}\\s?\\d{7,8}|13[0-9]\\d{8}|15[1089]\\d{8}";
    public boolean isValid(String value, ConstraintValidatorContext context)
    {
       if(StringUtil.isEmpty(value))
       {
           logger.error("telphone param is empty");
           return false;
       }
       return Pattern.matches(REGEX_TELEPHONE, value);
    }
}

使用

 @Telephone(message= "telphone invalid")
  private String telphone;

验证自定义规则

图片.png

总结

本来讲解了采用Spring Validation实现参数验证,大家一定要记住的是Validation只针对字段来进行相关的验证,切莫用于实现业务的复杂验证,这样违背了架构单一原则。