Spring Validation参数校验

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/xufei_0320/article/details/85169162

简介

Spring Validation是在Spring Context下的,在Spring Boot项目中,我们引入spring-boot-starter-web便会引入进来,Spring Validation是对Hibernate Validator的二次封装,使我们可以更方便的在Spring MVC中完成自动校验。

Hibernate Validator是对JSR-303(Bean Validation)的参考实现。Hibernate Validator 提供了JSR-303规范中所有内置constraint的实现,除此之外还有一些附加的constraint

JSR-303定义的constraint

Constraint Description
@Null 被注解的元素必须为null
@NotNull 被注解的元素必须不为null
@AssertTure 被注解的元素必须为ture
@AssertFalse 被注解的元素必须为false
@Min(value) 被注解的元素必须是数字且必须大于等于指定值
@Max(value) 被注解的元素必须是数字且必须小于等于指定值
@DecimalMin(value) 被注解的元素必须是数字且必须大于等于指定值
@DecimalMax(value) 被注解的元素必须是数字且必须小于等于指定值
@Size(max, min) 被注解的元素必须在指定的范围内
@Digits(integer, fraction) 被注解的元素必须是数字且其值必须在给定的范围内
@Past 被注解的元素必须是一个过去的日期
@Future 被注解的元素必须是一个将来的日期
@Pattern(value) 被注解的元素必须符合给定正则表达式

Hibernate Validator附加实现的constraint

Constraint Description
@Email 被注解的元素必须是Email地址
@Length(min, max) 被注解的元素长度必须在指定的范围内
@NotEmpty 被注解的元素必须
@Range 被注解的元素(可以是数字或者表示数字的字符串)必须在给定的范围内
@URL 被注解的元素必须是URL

当然,我们也可以自定义实现,自定义实现在下面使用中在讲吧。

使用

在开始使用之前,先做好准备工作,创建一个Spring Boot项目,然后引入依赖

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

只需要引入这个依赖就可以了。

使用@Validated注解拦截校验

Controller中,我们需要校验前端传递过来的参数,我们可以这么写

@RestController
public class TestController {

    @PostMapping("/test")
    public Object test(@RequestBody @Validated User user, BindingResult result) {
        if (result.hasErrors()) {
            return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        }
        return user;
    }
}

只需要在需要校验的实体前面打上@Validated注解就可以了,这时候,如果我们传递的参数符合要求,则会正常返回。否则返回:

[
    "age字段不合法",
    "name字段不合法"
]

它会将我们所有不合法信息一次性全部返回,在日常开发中,我们可以吧校验BindingResult是否有错误信息的校验统一抽出到一个工具类中去做处理,使用项目中统一格式返回错误信息就好。这就是一个最简单的校验示例了,其他注解也都是类似的,就不多举例了,可以自己尝试着玩玩。

在日常开发中想必都曾遇到过这样的需求,比如这个age这个字段,我想要这个字段只在PC端校验,在App端不做限制,这就需要用到分组校验了,每个注解都提供了一个group属性,利用这个属性就可以轻易做到以上需求。比如在User上的注解中加入group属性,指定其被校验的group

public class User {

    @Length(min = 1, max = 22, message = "name字段不合法", groups = {App.class, PC.class})
    private String name;
    @Min(value = 1, message = "age字段不合法", groups = PC.class)
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在Controller中的@Validated中指定当前group

@RestController
public class TestController {

    @PostMapping("/test")
    public Object test(@RequestBody @Validated(App.class) User user, BindingResult result) {
        if (result.hasErrors()) {
            return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        }
        return user;
    }
}

这时候我再使用两个不合法字段访问返回:

[
    "name字段不合法"
]

可以看到,它并没有对age字段进行校验。这就是它的分组校验。

在方法实现中拦截校验

它不只是在Controller校验前端传递过来的参数的时候可以用,它在方法中同样可以用,我们可以这样来使用:

@RestController
public class TestController {

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    SmartValidator smartValidator;

    @GetMapping("/test")
    public Object test() {
        String context = "{\"name\": \"felixu\",\"age\": 0}";
        User user = null;
        try {
            user = objectMapper.readValue(context, User.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
        BeanPropertyBindingResult result = new BeanPropertyBindingResult(user, "user");
        smartValidator.validate(user, result);
        if (result.hasErrors()) {
            return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        }
        return user;
    }
}

使用需要被校验的实体构造BeanPropertyBindingResult对象,然后将传递给SmartValidatorvalidate方法来完成跟上面相同的校验。validate有个重载方法,也接收分组,所以这种方式同样可以实现分组校验。

自定义实现

需求总是多变的,有时候,可能上面的校验方式并不能满足我们的要求,这时候就需要我们自定义一下校验了,要做到自定义注解来校验,我们需要做以下两步,首先实现ConstraintValidator<A extends Annotation, T>(ps:原谅我的自恋。。。):

public class IsFelixuValidator implements ConstraintValidator<IsFelixu, String> {

    @Override
    public void initialize(IsFelixu constraintAnnotation) {
        
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ("felixu".equals(value)) {
            return true;
        }
        return false;
    }
}

isValid便是我们的校验逻辑,true为通过校验。

然后我们实现注解:

@Documented
@Constraint(
    // 指定对应的校验类
    validatedBy = {IsFelixuValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsFelixu {

    String message() default "this value is not felixu";
    // 这两个属性必须要存在
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

这样就ok了,我们继续使用之前的来做测试,在Username属性上加上@IsFelixu注解,此时测试,如果不传递namefelixu的值,则会提示如下信息:

[
    "this value is not felixu",
    "age字段不合法"
]

总结

JSR-303 的发布使得在数据自动绑定和验证变得简单,使开发人员在定义数据模型时不必考虑实现框架的限制。当然Bean Validation还只是提供了一些最基本的constraint

上面只是相对简单的用法,也是我们现在项目中所用到的方式,在实际的开发过程中,用户可以根据自己的需要组合或开发出更加复杂的constraint。这就需要想象力了,从上面的用法中应该可以想到很多地方可以去使用,但是设计和实现时,往往需要考虑诸多因素,比如易用性和封装的复杂度,等等方面,还需要自己去考量了。

猜你喜欢

转载自blog.csdn.net/xufei_0320/article/details/85169162