使用Spring的Validator進行校驗


使用Spring的Validator進行校驗

單對象校驗

讓我們考慮一個小的數據對象:

import lombok.Data;

@Data
public class Person {

    private String name;
    private int age;
}

我們將通過實現以下兩個方法來提供Person類的驗證行為
org.springframework.validation.Validator接口的方法:

  • support(Class)-此驗證程序可以驗證提供的Class的實例嗎?
  • validate(Object,org.springframework.validation.Errors)-驗證給定對象,並在驗證錯誤的情況下,向給定Errors對象注冊

實施Validator非常簡單,尤其是當您知道Spring Framework也提供的ValidationUtils幫助器類時。

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
 * @author Created by niugang on 2020/3/30/16:45
 */
public class PersonValidator implements Validator {

    /**
     * This Validator validates *just* Person instances
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return Person.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) {
            e.rejectValue("age", "negative value","年齡不能為負值");
        } else if (p.getAge() > 110) {
            e.rejectValue("age", "too old","年齡不能超過110");
        }
    }

}

如您所見,ValidationUtils類上的靜態rejectIfEmpty(..)方法用於拒絕“名稱”屬性(如果該屬性為null或空字符串)。 看看ValidationUtils javadocs,看看它除了提供前面顯示的示例外還提供什么功能。

controller校驗

    @PostMapping(value = "persons")
    public void savePerson(@RequestBody Person person) {
        log.info("請求參數:{}", person);


//        PersonValidator validator = new PersonValidator();
//        if(validator.supports(Person.class)){
//            BindException errors = new BindException(person, "person");
//            validator.validate(person,errors);
//            List<ObjectError> allErrors = errors.getAllErrors();
//            log.info("size="+allErrors.size());
//            for (int i=0;i<allErrors.size();i++) {
//               log.info(allErrors.get(i).getCode());
//            }
//
//        }

        DataBinder binder = new DataBinder(person);
        binder.setValidator(new PersonValidator());
       // validate the target object
        binder.validate();
      // get BindingResult that includes any validation errors
        BindingResult results = binder.getBindingResult();
        log.info("results:{}", results);
    }
嵌套對象校驗

雖然可以實現單個Validator類來驗證豐富對象中的每個嵌套對象,但最好封裝每個嵌套類的驗證邏輯
對象在其自己的Validator實現中。 一個“豐富”對象的簡單示例是一個由兩個String屬性(第一個和第二個名稱)和一個復雜的Address對象組成的Customer。 地址對象可以獨立於客戶對象使用,因此已經實現了獨特的AddressValidator。 如果您希望CustomerValidator重用AddressValidator類中包含的邏輯,而無需進行復制和粘貼,則可以在您的CustomerValidator中依賴注入或實例化一個AddressValidator,並按如下方式使用它:

import lombok.Data;

/**
 * @author Created by niugang on 2020/3/30/18:42
 */
@Data
public class Address {

    private String  location;
}

import lombok.Data;

/**
 * @author Created by niugang on 2020/3/30/18:40
 */
@Data
public class Customer {

    private String firstName;
    private String surname;

    private Address address;
}

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
 * @author Created by niugang on 2020/3/30/18:44
 */
public class AddressValidator implements Validator {

    /**
     * This Validator validates *just* Address instances
     */
    @Override
    public boolean supports(Class<?> clazz) {
        return Address.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "location", "location.empty");
        Address p = (Address) obj;
        if (p != null && p.getLocation() != null && p.getLocation().length() > 5) {
            e.rejectValue("location", "value too length", "長度不能超過5");
        }
    }
}
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

/**
 * @author Created by niugang on 2020/3/30/18:47
 */
public class CustomerValidator implements Validator {
    private final Validator addressValidator;

    public CustomerValidator(Validator addressValidator) {

        if (addressValidator == null) {
            throw new IllegalArgumentException("The supplied [Validator] is " +
                    "required and must not be null.");
        }
        if (!addressValidator.supports(Address.class)) {
            throw new IllegalArgumentException("The supplied [Validator] must " +
                    "support the validation of [Address] instances.");
        }
        this.addressValidator = addressValidator;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}

將代碼解析為錯誤消息

我們已經討論了數據綁定和驗證。輸出與驗證錯誤相對應的消息是我們需要討論的最后一件事。在上面顯示的示例中,我們拒絕了姓名和年齡字段。如果我們要使用MessageSource輸出錯誤消息,則將使用拒絕字段時給出的錯誤代碼(在這種情況下為“名稱”和“年齡”)進行輸出。當您從Errors接口調用(直接或間接使用,例如ValidationUtils類)rejectValue或其他拒絕方法之一時,底層實現不僅會注冊您傳入的代碼,還會注冊許多其他錯誤代碼。它注冊的錯誤代碼由所使用的MessageCodesResolver確定。默認情況下,使用DefaultMessageCodesResolver,例如,它不僅使用您提供的代碼注冊消息,而且還包括包含您傳遞給拒絕方法的字段名稱的消息。因此,如果您使用rejectValue(“ age”,“ too.darn.old”)拒絕字段,除了too.darn.old代碼外,Spring還將注冊too.darn.old.age和too.darn.old .age.int(因此第一個將包含字段名稱,第二個將包含字段類型);這樣做是為了方便開發人員,以幫助他們定位錯誤消息等。

Spring Validator

Spring 3對其驗證支持進行了一些增強。 首先,現在完全支持JSR-303 Bean驗證API。 其次,以編程方式使用時,Spring的DataBinder現在可以驗證對象以及綁定到它們。 第三,Spring MVC現在支持聲明式驗證@Controller輸入。

JSR-303 Bean驗證API概述

JSR-303標准化了Java平台的驗證約束聲明和元數據。 使用此API,您可以使用聲明性驗證約束和運行時注釋域模型屬性強制執行。 您可以利用許多內置約束。 您也可以定義自己的自定義約束。

單對象

為了說明這一點,請考慮一個具有兩個屬性的簡單PersonForm模型:

public class PersonForm {
private String name;
private int age;
}

JSR-303允許您針對以下屬性定義聲明性驗證約束:

public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
 @PostMapping(value = "personforms.do")
 public void saveCustomer(@RequestBody @Validated PersonForm personForm) {
     log.info("請求參數:{}", personForm);
 }

聯級校驗

Bean Validation API不僅允許驗證單個類實例,而且還可以完成對象圖(級聯驗證)。 為此,只需注釋表示一個
如級聯驗證所示,使用@Valid引用另一個對象。

import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 * @author Created by niugang on 2020/3/30/19:42
 */
@Data
public class Car {

    @NotNull
    private String type;

    @NotNull
    @Valid
    private PersonInfo driver;
}

import lombok.Data;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * @author Created by niugang on 2020/3/30/19:42
 */
@Data
public class PersonInfo {

    @NotNull
    @Size(min = 5)
    private String name;
}


聯級校驗集合

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.constraints.Min;

/**
 * @author Created by niugang on 2020/4/2/11:32
 */
@Data
public class Dog {


    @NotBlank
    private  String  name;

    @NotBlank
    private  String type;

    @Min(0)
    private int age;
}

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Created by niugang on 2020/4/2/11:31
 */
@Data
public class Animal {


    @Min(1)
    private int count;


    @Valid
    @NotNull
    @Size(min = 1)
    private List<Dog> dogs= new ArrayList<>();

}

組校驗

Driver中的類Driver擴展Person並添加屬性age和hasDrivingLicense。 駕駛員必須年滿18歲(@Min(18))並具有駕駛執照(@AssertTrue)。 這些屬性上定義的兩個約束都屬於該組DriverChecks只是一個簡單的標記界面。

import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * @author Created by niugang on 2020/4/2/14:44
 */
@Data
public class Person {
    @NotNull
    private String name;
}

import lombok.Data;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;

/**
 * @author Created by niugang on 2020/4/2/14:45
 */
@Data
public class Driver extends Person {
    @Min(
            value = 18,
            message = "You have to be 18 to drive a car",
            groups = DriverChecks.class
    )
    public int age;
    @AssertTrue(
            message = "You first have to pass the driving test",
            groups = DriverChecks.class
    )
    public boolean hasDrivingLicense;
}

/**
 * @author Created by niugang on 2020/4/2/14:45
 */
public interface DriverChecks {
}

controller

以下請求 只會對標記為DriverChecks的屬性進行校驗

  @PostMapping(value = "drivers.do")
    public void cars(@RequestBody @Validated(DriverChecks.class) Driver driver, BindingResult result) {
        log.info("請求參數:{}", driver);
        log.info("請求參數 result:{}", result);
    }

配置Bean驗證提供程序

Spring提供了對Bean驗證API的全面支持。 這包括對將JSR-303 / JSR-349 Bean驗證提供程序引導為Spring Bean的便捷支持。 這允許在應用程序中需要驗證的任何地方插入javax.validation.ValidatorFactory或javax.validation.Validator。

使用LocalValidatorFactoryBean將默認的驗證器配置為Spring Bean:

<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

上面的基本配置將觸發Bean驗證使用其默認的引導程序機制進行初始化。 預期Hibernate Validator之類的JSR-303 / JSR-349提供程序將出現在類路徑中,並將自動檢測到該提供程序。

注入驗證器

LocalValidatorFactoryBean同時實現javax.validation.ValidatorFactory和javax.validation.Validator以及Spring的
org.springframework.validation.Validator。 您可以將對這兩個接口之一的引用注入需要調用驗證邏輯的Bean中。

如果您希望使用Bean驗證,請注入對javax.validation.Validator的引用API直接:

import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;

如果您的bean需要Spring Validation API,請注入對org.springframework.validation.Validator的引用:

@RestController
@RequestMapping("/validator/")
@Slf4j
public class TestValidatorController{
    
    private final Validator validator;

    @Autowired
    public TestValidatorController(Validator validator) {
        this.validator = validator;
    }
    
      @PostMapping(value = "v2/drivers.do")
      public void carss(@RequestBody Driver driver) {
        BindException bindException = new BindException(driver, "driver");
        validator.validate(driver,bindException);
        log.info("請求參數:{}", driver);
        log.info("請求參數 result:{}", bindException.getBindingResult());
    }

}
@Data
public class Driver {

    @NotNull
    private String name;
    @Min(
            value = 18,
            message = "You have to be 18 to drive a car"
    )

    public int age;

    @AssertTrue(
            message = "You first have to pass the driving test"
    )

    public boolean hasDrivingLicense;

}

配置自定義約束

每個Bean驗證約束均由兩部分組成。 首先,@Constraint批注聲明約束及其可配置屬性。 二,實施實現約束行為的javax.validation.ConstraintValidator接口。 要將聲明與實現相關聯,每個@Constraint批注都引用一個對應的ValidationConstraint實現類。 在運行時,當在域模型中遇到約束注釋時,ConstraintValidatorFactory實例化引用的實現。

默認情況下,LocalValidatorFactoryBean配置一個使用Spring創建ConstraintValidator實例的SpringConstraintValidatorFactory。 這使您的自定義ConstraintValidators可以像其他任何Spring Bean一樣受益於依賴項注入

下面顯示的是一個自定義@Constraint聲明的示例,后面是一個使用Spring進行依賴項注入的關聯的ConstraintValidator實現:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
...
}

如您所見,ConstraintValidator實現可能像其他任何Spring bean一樣具有@Autowired的依賴項。

基於spring的方法校驗

即就是在方法上寫注解,進行參數校驗

Bean Validation 1.1支持的方法驗證功能以及Hibernate Validator 4.3的自定義擴展也可以通過以下方式集成到Spring上下文中:MethodValidationPostProcessor bean的定義:

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

為了有資格進行Spring驅動的方法驗證,所有目標類都需要使用Spring的@Validated注釋進行注釋,並可以選擇聲明要使用的驗證組。 看看使用MethodValidationPostProcessor javadocs進行Hibernate Validator和Bean Validation 1.1提供程序的設置詳細信息

配置一個DataBinder

從Spring 3開始,可以使用Validator配置DataBinder實例。 配置完成后,可以通過調用binder.validate()來調用Validator。 任何驗證錯誤都會自動添加到BindingResult。
以編程方式使用DataBinder時,可將其用於在綁定到目標對象后調用驗證邏輯:

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM