参数校验之Hibernate-validator的基本使用

Hibernate-validator是对validation-api的再次封装,在开发过程中是经常使用到(spring boot里面的spring-boot-starter-web是默认应用了Hibernate-validator包的),特别是参数校验,刚开始做开发工作的时候,都是在Service层用if…else…来判断参数的合法性,这个会使代码显得很臃肿,后来接触Hibernate-validator,真的是很好用的。这里分享出来,也是做个笔记,以后用到可以作为参考资料。

validation常用的注解和核心类

validation-api中的注解比较全,Hibernate-validator也有自己的一些注解,都是一些很好用的。

POM依赖坐标
<dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.0.18.Final</version>
</dependency>
validation-api原生注解
  • @AssertTrue、@AssertFalse:作用在布尔类型的属性上,校验其值是true或者false
  • @DecimalMax、@DecimalMin:支持BigDecimal、BigInteger等类型,限定其最大值和最小值,包含界限值
  • @Min、@Max:支持int、BigDecimal、BigInteger等类型,限定最大值和最小值,包含界限值
  • @Digit:作用在数字上,有两个参数,分别是integer和fraction,integer用来限定小数点前的位数,fraction用来限定小数点后的位数
  • @Future:作用在日期类型上,根据官网的说法是Date和Calendar,要求传入的时间是未来的时间
  • @Null:用来表示当前字段是否为null
  • @Past:此注解和Future是相反的,要求传入的时间是过去的时间
  • @Size:作用在Collection集合、Map集合以及CharSequence类型(String是CharSequence子类),注解内有两个参数,分别是min、max,作为长度的上限和下限,包含上下限值
  • @Pattern:通过正则表达式表达式校验传入参数是否符合要求
Hibernate-validator封装注解
  • @NotBlank:这个注解其实不是validation包里面的,如果需要用它,需另外引入hibernate-validator包,用来限定属性不能为null和空串,一般都是用在String类型属性上,此注解会将传入的字符串的前面和后面的空清除,相当于执行了trim操作
  • @NotEmpty:限定属性不能为空,需要和@NotBlank注解区分开,两者有所不同,此注解不会执行trim操作
  • @Email:用来校验传入的数据是否符合邮箱格式的
  • @Range:范围值,包含两个主要的两个属性min和max,这个相当于对validator-api@Min@Max的再次封装,将两个注解合并为一个注解,包含边界值
其他说明

Hibernate-validator的注解不止这么多,还有如@SafeHtml@ScriptAssert等,这些在日常很少用,就不多去赘述了,感兴趣的可以研究一下,其实从字面上也能理解一二。

核心类

上面的注解校验结束后,会将结果封装到这个核心类里面,通过这个类提供的API获取是否有校验不通过的字段信息。核心类的名称是BindingResult,这个类是Spring封装的,在spring-context包内。

主要使用的两个API:

  • hasErrors():返回值为boolean类型,true表示由校验未通过的属性
  • getFieldError().getDefaultMessage():获取错误信息,也就是校验上面的注解中message属性对应的值

对象属性的校验

简单属性校验

对一个类中的几个非集合,非子对象的属性校验,这些校验是最常见的,也是最简单的。

  • 校验对象
@Data
public class ValidDemo {
    @Size(min = 2, max = 5, message = "长度不符合要求")
    private String name;
    @Min(value = 10,message = "不合法年龄")
    private Integer age;
}
  • 控制层方法的书写方式
@PostMapping("/post")
@ResponseBody
public String valid1(@RequestBody @Valid ValidDemo validDemo, BindingResult bindingResult) {
    return bindingResult.hasErrors() ? bindingResult.getFieldError().getDefaultMessage() : "success";
}

需要注意点

  • 控制层方法中,要在需要校验的对象上加上@Valid注解,否则被校验对象里面的相关校验注解都是不能生效的
  • 控制层方法中,需要添加一个参数BindingResult,用来接收校验的结果,给出前端友好的响应
  • 这里的简单类型主要指的是基本类型、基本类型包装类和String
  • 另外最需要注意的点就是被校验对象,它不是基本类型、基本类型包装类和String,具体原因后面会继续说到
复杂属性校验

所谓的复杂类型,就是对象里面的属性不是简单的基本类型、基本类型包装类和String,而是包含Collection集合、嵌套对象等。

  • 校验对象
@Data
public class ValidDemo {
    @Size(min = 2, max = 5, message = "长度不符合要求")
    private String name;
    @Min(value = 10, message = "不合法年龄")
    private Integer age;
    @NotNull(message = "用户列表不能为空")
    @Valid
    private List<User> users;
    @NotNull(message = "订单信息不能为空")
    @Valid
    private Order order;
}
//内嵌的用户对象
@Data
public class User {
    @NotBlank(message = "地址不能为空")
    private String addr;
    @Email(message = "邮箱格式不正确")
    private String email;
}
//内嵌的订单对象
@Data
public class Order {
    @DecimalMin(value = "1.00", message = "价格不正确")
    private BigDecimal price;
}
  • 控制层方法
@GetMapping("/get")
@ResponseBody
public String valid1(@ModelAttribute @Valid ValidDemo validDemo, BindingResult bindingResult) {
    return bindingResult.hasErrors() ? bindingResult.getFieldError().getDefaultMessage() : "success";
}

需要注意点

  • 复杂对象,里面嵌套Collection集合(List集合),必须在集合上加@Valid注解,否则集合内的对象(User)中属性加的注解不会生效
  • 同上理里面嵌套的对象(Order对象)上也是需要加上@Valid注解
  • 控制层方法,这里使用了GET请求,接收参数没有使用字段直接接收,而是使用对象,需要在对象上加@ModelAttribute注解,将GET请求的字段封装到此对象中,为什么这么做而不用单子段接收?下面说具体原因
GET和POST请求的区别

上面的两个例子说完了,在注意点上都留下了一个问题。为什么控制层都使用对象来接收参数,POST请求用对象接收是可以理解的,而GET请求更常用的使用字段来接收,但是为什么不用。

看个例子:

@GetMapping("/error")
@ResponseBody
public String error(@RequestParam("name") @Size(min = 2, max = 5, message = "名字长度不合法") String name) {
    return "success";
}

控制层这么写,调试可以得到,这样写@Size是不会生效的。

但是又有人会觉得应该加上@Valid注解,但是事实依然不会生效。(想验证的可以尝试一下)

如果你必须用字段来接收GET请求的参数,且需要使用validation的校验,接下来给你一个不建议的校验方式。

不建议的校验方式

描述不多做赘述,直接上代码啦!

  • 第一步:在Spring容器中添加一个Bean:MethodValidationPostProcessor
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    return new MethodValidationPostProcessor();
}
  • 第二步:在需要校验的方法所在类上添加@Validated注解,注意是类上面,不是方法上。
@Validated
public class ValidatorController {}
  • 第三步:在方法上加上校验注解
@GetMapping("/error")
@ResponseBody
public String error(@RequestParam("name") @Size(min = 2, max = 5, message = "名字长度不合法") String name) {
    return "success";
}

OK,到此就完成了,当校验不通过的时候,会给出以下信息:

{
    "timestamp": 1556769001783,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "javax.validation.ConstraintViolationException",
    "message": "No message available",
    "path": "/valid/error"
}

到这里多少能看出来一点问题了,下面来罗列一下:

  • 响应信息不是给出注解里面已经预设好的"名字长度不合法",而是一串异常信息,这个有点尴尬,这个直接响应给前端,会不会被骂死,你看着办吧。(也许你可以用切面去处理,但是不是有点太麻烦)
  • 正常一个控制层的Controller里面都会有很多方法,其他方法(如POST)是用对象来接收的参数的,用不到这个注解,直接使用@Valid就可以。但是不好意思,@Validated注解是写在方法上的,作用于全局,会导致@Valid不起作用,也就是说当前这个Controller所有的方法返回的都是上面的异常信息。为了兼容一个GET接收字段参数,把整个方法的校验规则都打乱了,是不是有点得不偿失。因此这种方式的校验是不推荐的,在上面复杂校验介绍里面其实已经给出解决方案了,不管是GET、POST还是其他类型的请求,都统一用对象接收。另外用对象接收是有好处的,比如一个GET查询有很多查询条件,如果都写在方法上会让整个方法很长,如果在传参的过程中,不小心把参数顺序弄错了,那就尴尬啦。用对象封装接收更科学更方便,个人觉得。

自定义校验规则

上面的校验规则都达不业务校验的要求,我需要有一个自定义的校验规则,那也是可以的,接下来说一下自定义校验规则的写法。

  • 自定义注解
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = MapValidConstraint.class)
public @interface MapValidNull {

    int value() default -1;

    String message() default "Map集合不能为空";

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

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

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        MapValidNull[] value();
    }
}
  • 注解处理类
// 泛型的第一个是对应的注解,第二个是字段属性类型
@Component
public class MapValidConstraint implements ConstraintValidator<MapValidNull, Map> {

    private int value;

    @Override
    public void initialize(MapValidNull constraintAnnotation) {
        this.value = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(Map map, ConstraintValidatorContext context) {
        //TODO 具体判断逻辑……
        return true;
    }
}
  • 然后及时使用注解,和预定义的注解使用方式是一样的。
@MapValidNull
private Map<String,String> map;

业务校验工具

直接在Controller中写检查注解+BindingResult基本就能满足日常开发需求,但是还有一种情况下是满足不了的,就是非HTTP接口,在使用Dubbo或者其他RPC调用的服务就不能使用这种方式,需要另外的在业务代码中进行校验。这个时候就无法使用BindingResult来直接获取校验结果。怎么办呢?

Hibernate-validator里面可以手动的执行校验,使用到的工具类是Validator。简单看一下这个类的源码如下:

public interface Validator {

	//直接检查整个类中的所有字段
	<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);

	//指定检查类中某个字段
	<T> Set<ConstraintViolation<T>> validateProperty(T object,
													 String propertyName,
													 Class<?>... groups);

	<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,
												  String propertyName,
												  Object value,
												  Class<?>... groups);

	BeanDescriptor getConstraintsForClass(Class<?> clazz);

	<T> T unwrap(Class<T> type);

	ExecutableValidator forExecutables();
}

使用到的是前两个方法,一般使用到第一个就足够啦。下面看一下检查的具体逻辑。

  • 首先需要检查的字段上需要加上注解,可以是Hibernate-validator里面自带的注解,也可以是自定义注解

  • 在业务代码中获取Validator实例,调用validate方法,将类作为参数传入进去,如下代码:

    //构建Validator实例
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();
    //调用校验方法
    Article article = new Article();
    Set<ConstraintViolation<T>> validates = validator.validate(artilce); //调用校验方法
    if(CollectionUtils.isEmpty(validates)) System.out.println("全部检查通过");
    return validates.iterator().next().getMessage(); //取第一个错误信息返回
    
  • 如果只是校验类中的某个字段,调用validateProperty方法,传入需要检查的对象和需要检查的字段名称,如下代码:

    //构建Validator实例
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    Validator validator = factory.getValidator();
    //调用校验方法
    Article article = new Article();
    Set<ConstraintViolation<T>> validates = validator.validate(artilce,"title"); //调用校验方法,只检查title字段
    if(CollectionUtils.isEmpty(validates)) System.out.println("检查通过");
    return validates.iterator().next().getMessage(); //取第一个错误信息返回
    

这种检查方式还是会经常用到的,每次都去获取Validator实例以及判断检查结果,代码会比较臃肿,所以这里可以封装一个工具类。(可以根据实际的项目对这个工具类进行修改)

public class ValidatorUtil {

    public final static String VALIDATE_PASS = "VALIDATE_PASS";

    private static Validator validator;

    static {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
    }

    /**
     * 校验对象内所有的字段
     *
     * @param validateBean 被检查的对象
     * @param <T>          对象类型
     * @return 成功返回固定信息:VALIDATE_PASS,失败默认返回第一个异常信息
     */
    public static <T> String validate(T validateBean) {
        Set<ConstraintViolation<T>> validates = validator.validate(validateBean);
        if (CollectionUtils.isEmpty(validates)) return VALIDATE_PASS;
        return validates.iterator().next().getMessage();
    }

    /**
     * 校验对象内的指定字段
     *
     * @param validateBean 被检查的对象
     * @param propertyName 指定字段名称
     * @param <T>          对象类型
     * @return 成功返回固定信息:VALIDATE_PASS,失败默认返回第一个异常信息
     */
    public static <T> String validateProperty(T validateBean, String propertyName) {
        Set<ConstraintViolation<T>> validates = validator.validateProperty(validateBean, propertyName);
        if (CollectionUtils.isEmpty(validates)) return VALIDATE_PASS;
        return validates.iterator().next().getMessage();
    }
}

总结

到这里关于Hibernate-validator的使用就写完啦,主要说的是下面几点内容。

  • 介绍Hibernate-validatorvalidation-api中已经定义好的检查注解,以及这些注解的基本使用规则
  • 基于HTTP接口的参数校验,GET、POST请求都有涉及到,并且特殊说明接收参数是对象还是单个字段存在的区别
  • 说明为什么不用单个字段接收参数的问题
  • 自定义检查注解,实现个性化的检查规则
  • 基于非HTTP接口服务,实现用工具类在业务代码中完成参数校验
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Hibernate Validator是一个用于Java Bean验证的框架,它提供了一组注解和API,用于验证Java Bean的属性值是否符合指定的规则和约束。它可以用于验证表单数据、REST API请求、数据库实体等各种场景下的数据。Hibernate Validator支持多种验证规则,包括基本数据类型、字符串、日期、集合、数组等。它还支持自定义验证规则和错误消息。使用Hibernate Validator可以有效地减少代码量和提高开发效率。 ### 回答2: Hibernate Validator是一个流行的Java开源校验框架,它是基于JSR 303规范(Bean验证)的实现。它提供了一组注释和API,用于对JavaBean进行验证和校验Hibernate Validator提供了很多内置的校验注释,比如@NotNull、@NotEmpty、@Min、@Max等。这些注释可以直接应用在JavaBean的属性上,通过注释指定的校验规则来验证属性的值是否合法。同时,Hibernate Validator还支持自定义校验注释,可以根据业务需求来定义新的注释,并实现相应的校验逻辑。 Hibernate Validator不仅可以对单个属性进行校验,还支持对整个JavaBean进行校验。例如,可以在JavaBean的类级别上添加@Valid注释,来验证整个对象的合法性。此外,还可以通过分组校验来实现在不同场景下不同的校验规则。 除了注释方式外,Hibernate Validator还提供了一套强大的API,通过编程方式来进行校验和验证。通过ValidatorFactoryValidator两个主要的接口,可以创建Validator对象并执行校验操作。可以验证单个属性的值,也可以验证整个JavaBean对象。 Hibernate Validator还提供了国际化的支持,可以根据不同的区域设置显示不同的错误信息。同时,还能够将校验错误信息与具体的校验注释进行关联,方便开发者快速定位问题。 总结来说,Hibernate Validator提供了一种有效、灵活和方便的方式来对JavaBean进行校验。它的注释和API丰富多样,并且支持自定义校验规则,同时还提供了国际化和错误信息关联等特性,使得校验过程更加强大和可控。它在Java开发中的应用越来越广泛,为开发者提供了一种便捷的校验解决方案。 ### 回答3: Hibernate Validator是一个基于JSR 380规范的校验框架,它可以轻松地对Java对象进行校验。通过使用Hibernate Validator,开发人员可以在应用程序中方便地添加校验规则,并且可以验证这些规则是否被满足。 Hibernate Validator提供了一组注解,这些注解可以附加在JavaBean的属性上,以标识需要进行校验的规则。例如,@NotNull注解用于确保属性的值不为空,@Size注解用于确保字符串类型的属性的长度在指定范围内,等等。除了注解外,Hibernate Validator还提供了一些内置的校验器,用于验证各种数据类型的属性,例如字符串、数字、日期等。 使用Hibernate Validator进行校验非常简单。只需要在需要校验JavaBean上添加注解,并在需要校验的时候调用校验方法即可。校验方法会返回一个包含校验结果的对象,开发人员可以根据需要进行处理。校验方法还可以接受一个可选的校验分组参数,用于校验不同场景下的不同规则。 Hibernate Validator还提供了一些扩展功能,用于自定义校验规则。开发人员可以创建自定义的校验注解,并编写相应的校验器来实现特定的校验逻辑。这使得Hibernate Validator非常灵活,可以满足各种不同的校验需求。 总结来说,Hibernate Validator是一个强大而灵活的校验框架,能够方便地对Java对象进行校验使用Hibernate Validator可以增加应用程序的稳定性和可靠性,减少错误和异常的发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿洞晓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值