1. 程式人生 > >Spring Validation引數校驗

Spring Validation引數校驗

簡介

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。這就需要想象力了,從上面的用法中應該可以想到很多地方可以去使用,但是設計和實現時,往往需要考慮諸多因素,比如易用性和封裝的複雜度,等等方面,還需要自己去考量了。