兩種方式實現Spring 業務驗證

  • 2019 年 10 月 5 日
  • 筆記

這是 cxuan 的第33篇原創文章

驗證在任何時候都非常關鍵。考慮將數據驗證作為業務邏輯開發有利也有弊,Spring 認為,驗證不應該只在Web 端進行處理,在服務端也要進行相應的處理,可以防止臟數據存入資料庫中,從而避免為運維同學和測試同學造成更大的困擾,因為數據造成的bug會更加難以發現,而且開發人員關注點也不會放在數據本身的問題上,所以做服務端的驗證也是非常有必要的。考慮到上面這些問題,Spring 提供了兩種主要類型的驗證:

  • 一個是實現Validator 介面來創建自定義驗證器,用於服務端數據校驗。
  • 一種是通過Spring 對Bean Validation 支援實現的。

通過使用 Spring Validator 介面進行驗證

Spring 提供 Validator 介面用於驗證對象。Validator 介面通過使用 Errors 對象來工作,以便在驗證時,驗證器可以向 Errors 對象報告驗證失敗。下面是一個簡單的 對象示例

public class Person {        private String name;      private int age;        // get and set...  }  

下面一個例子為 Person 對象提供了一種驗證方式,通過實現了 org.springframework.validation.Validator 介面 的兩個方法:

  • supports(Class): 表示此 Validator 是否能夠驗證提供的類的實例
  • validate(Object, org.springframework.validation.Errors): 驗證給定的對象,如果驗證錯誤,則註冊具有給定 Errors 對象。

實現一個 Validator 非常簡單,而且Spring 也提供了 ValidationUtils 工具類幫助進行驗證。下面是一個驗證 Person 對象的例子:

@Component  public class PersonValidator implements Validator {        // 此 Validator 只驗證 Person 實例      public boolean supports(Class clazz) {          return Person.class.equals(clazz);      }        public void validate(Object obj, Errors e) {          ValidationUtils.rejectIfEmpty(e, "name", "name.empty");      }  }  

上面程式碼示例中的靜態方法 rejectIfEmpty() 方法用於拒絕name屬性,當name 屬性是 null 或者是 空串的時候。查看 ValidationUtils 文檔關於它能夠提供的功能。

然後再來編寫配置類 AppConfig:

@Configuration  @ComponentScan("com.spring.validation")  public class AppConfig {}  

使用 @Configuration 註解聲明此類為配置類(更多 @Configuration 的用法,請參照 原創 | 我被面試官給虐懵了,竟然是因為我不懂Spring中的@Configuration )

配置@ComponentScan 註解用於自動裝配,默認是使用 basePackages 掃描指定包,字元串表示。

然後對上面的程式進行驗證

public class SpringValidationApplicationTests {        public static void main(String[] args) {          AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);            Person person = new Person();          person.setAge(18);          person.setName(null);            PersonValidator personValidator = applicationContext.getBean("personValidator", PersonValidator.class);          BeanPropertyBindingResult result = new BeanPropertyBindingResult(person,"cxuan");            ValidationUtils.invokeValidator(personValidator,person,result);            List<ObjectError> allErrors = result.getAllErrors();          allErrors.forEach(e-> System.out.println(e.getCode()));      }  }  

因為是基於註解的配置,所以使用 AnnotationConfigApplicationContext上下文啟動類,把配置類 AppConfig 當作參數,然後構建一個Person 類,為了測試驗證有效性,把 name 設置為 null,然後通過上下問的 getBean 方法獲得 personValidator 的實例,通過使用 BeanPropertyBindingResult 把 person 綁定為 cxuan 的名字,然後使用 ValidationUtils 工具類進行驗證,最後把驗證的結果進行檢查。

上面程式經驗證後的結果如下:

org.springframework.validation.ValidationUtils – Invoking validator [com.spring.validation.PersonValidator@37918c79] DEBUG org.springframework.validation.ValidationUtils – Validator found 1 errors name.empty

使用 Bean Validation 進行驗證

從 Spring4 開始,就已經實現對 JSR-349 Bean Validation 的全面支援。Bean Validation API 在 javax.validation.constraints 包中以 Java 註解(例如 @NonNull) 形式定義了一組可用域對象的約束。

通過使用 Bean Validation API ,可以避免耦合到特定的驗證服務提供程式。Spring 對 Bean Validation API 提供了無縫支援,主要使用一些註解進行驗證,下面一起來看一下

定義對象屬性上的驗證約束

首先,將驗證約束應用於域對象屬性。使用maven 配置需要引入對應的依賴

<dependency>      <groupId>javax.validation</groupId>      <artifactId>validation-api</artifactId>      <version>1.1.0.Final</version>  </dependency>      <dependency>      <groupId>org.hibernate</groupId>      <artifactId>hibernate-validator</artifactId>      <version>5.2.4.Final</version>  </dependency>  

之後定義了一些實體類,使用 javax.validation.constraints 包中的注釋進行標註

public class Singer {        @NotNull      @Size(min = 2,max = 60)      private String firstName;        private String lastName;        @NotNull      private Genre genre;        private Gender gender;        get and set...  }  

對於 firstName ,定義了兩個約束,第一個約束由 @NotNull 進行控制,它表示該值不能為空。此外,@Size註解控制著 firstName 的長度在 2 – 60 之間。@NotNull 還用於 genre 屬性。下面是GenreGender 的枚舉類

public enum Genre {        POP("P"),      JAZZ("J"),      BLUES("B"),      COUNTRY("C");        private String code;        private Genre(String code){          this.code = code;      }        public String toString(){          return this.code;      }  }    public enum Gender {        MALE("M"),      FEMALE("F");        private String code;        Gender(String code){          this.code = code;      }        @Override      public String toString() {          return this.code;      }  }  

Genre 表示歌手所屬的音樂類型,而 Gender 與音樂事業不相關,所以可以為空

在 Spring 中配置 Bean Validation 支援

為了在 Spring 的 ApplicationContext 中配置對 Bean Validation API 的支援,可以在Spring 的配置中定義一個 LocalValidatorFactoryBean 的 bean如下

@Configuration  @ComponentScan("com.spring.validation")  public class ValidationConfig {        @Bean      LocalValidatorFactoryBean validatorFactoryBean(){          return new LocalValidatorFactoryBean();      }  }  

聲明一個 LocalValidatorFactoryBean 的 bean 是必須的。默認情況下,Spring 會在類路徑下搜索 Hibernate Validator庫,驗證它是否存在。

下面我們編寫一個為 Singer 類提供驗證服務的服務類

@Service  public class SingerValidationService {        @Autowired      private Validator validator;        public Set<ConstraintViolation<Singer>> validateSinger(Singer singer){          return validator.validate(singer);      }  }  

注入一個 javax.validation.Validator 實例(請注意與 Spring 提供的 Validator 介面不同)。一旦定義了 LocalValidatorFactoryBean ,就可以在應用程式中的任意位置創建 Validator 的句柄。要在 POJO 上進行驗證,需要調用 validator.validate 方法,驗證結果以 ConstraintViolation<T> 介面的集合形式返回。下面是上面例子程式的驗證

public class SpringBeanValidationTest {        public static void main(String[] args) {          AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ValidationConfig.class);            SingerValidationService singerBean = applicationContext.getBean(SingerValidationService.class);            Singer singer = new Singer();          singer.setFirstName("c");          singer.setLastName("xuan");          singer.setGenre(null);          singer.setGender(null);            validateSinger(singer,singerBean);            applicationContext.close();      }        private static void validateSinger(Singer singer,SingerValidationService singerValidationService){          Set<ConstraintViolation<Singer>> violationSet = singerValidationService.validateSinger(singer);          listViolations(violationSet);      }        private static void listViolations(Set<ConstraintViolation<Singer>> violations){          System.out.println("violations.size() = " + violations.size());          for(ConstraintViolation<Singer> violation : violations){              System.out.println("Validation error for property : " + violation.getPropertyPath());              System.out.println("with value : " + violation.getInvalidValue());              System.out.println("with error message : " + violation.getMessage());          }      }  }  

上述程式碼構建了一個 Singer 類進行驗證,因為 firstname 屬性的要求是長度介於 2 – 60 之間並且不能為null,所以這裡只用了一個字元驗證,genre 屬性不能為null,最核心的驗證方法就是 singerValidationService.validateSinger(singer).方法,它會調用

public Set<ConstraintViolation<Singer>> validateSinger(Singer singer){    return validator.validate(singer);  }  

進行驗證,驗證的結果返回的是 ConstraintViolation<Singler>類型,然後把對應的錯誤資訊輸出,上面的錯誤資訊是

violations.size() = 2 Validation error for property : firstName with value : c with error message : 個數必須在2和60之間 Validation error for property : genre with value : null with error message : 不能為null

可以列印出兩個錯誤,並輸出錯誤的屬性、值以及錯誤資訊。

你點的每個好看,我都認真當成了喜歡