Java的業務邏輯驗證框架fluent-validator

Java的業務邏輯驗證框架fluent-validator

  • 二月 8, 2016html

1 背景

在互聯網行業中,基於Java開發的業務類系統,無論是服務端仍是客戶端,業務邏輯代碼的更新每每是很是頻繁的,這源於功能的快速迭代特性。在通常公司內部,特別是使用Java web技術構建的平臺中,無論是基於模塊化仍是服務化的,業務邏輯都會相對複雜。java

這些系統之間、系統內部每每存在大量的API接口,這些接口通常都須要對入參(輸入參數的簡稱)作校驗,以保證:
1) 核心業務邏輯可以順利按照預期執行。
2) 數據可以正常存取。
3) 數據安全性。包括符合約束以及限制,有訪問權限控制以及不出現SQL注入等問題。git

開發人員在維護核心業務邏輯的同時,還須要爲輸入作嚴格的校驗。當輸入不合法時,可以給caller一個明確的反饋,最多見的反饋就是返回封裝了result的對象或者拋出exception。程序員

一些常見的驗證代碼片斷以下所示:github

public Response execute(Request request) {
    if (request == null) {
        throw BizException();
    }     List cars = request.getCars();
    if (CollectionUtils.isEmpty(cars)) {
        throw BizException();
    }     for (Car car : cars) {
        if (car.getSeatCount() < 2) {
            throw BizException(); 
        }
    }     // do core business logic}

咱們不能說這是反模式(anti-pattern),可是從中咱們能夠發現,它不夠優雅並且違反一些範式:
1)違反單一職責原則(Single responsibility)。核心業務邏輯(core business logic)和驗證邏輯(validation logic)耦合在一個類中。
2)開閉原則(Open/closed)。咱們應該對擴展開放,對修改封閉,驗證邏輯很差擴展,並且一旦須要修改須要動總體這個類。
3)DRY原則(Don’t repeat yourself)。代碼冗餘,相同邏輯可能散落多處,久而久之很差收殮。web

 

2 爲什麼要使用FluentValidator

緣由很簡單,第一爲了優雅,出色的程序員都有點潔癖,都但願讓驗證看起來很舒服;第二,爲了盡最大可能符合這些優秀的原則,作clean code。spring

FluentValidator就是這麼一個工具類庫,適用於以Java語言開發的程序,讓開發人員迴歸focus到業務邏輯上,使用流式(Fluent Interface)調用風格讓驗證跑起來很優雅,同時驗證器(Validator)能夠作到開閉原則,實現最大程度的複用。數據庫

 

3 FluentValidator特色

這裏算是Quick learn瞭解下,也當且看作打廣告吧,看了這些特色,但願能給你往下繼續閱讀的興趣:)express

1) 驗證邏輯與業務邏輯再也不耦合
    摒棄原來不規範的驗證邏輯散落的現象。api

2) 校驗器各司其職,好維護,可複用,可擴展
    一個校驗器(Validator)只負責某個屬性或者對象的校驗,能夠作到職責單一,易於維護,而且可複用。

3) 流式風格(Fluent Interface)調用
    藉助Martin大神提倡的流式API風格,使用「惰性求值(Lazy evaluation)」式的鏈式調用,相似guavaJava8 stream API的使用體驗。

4) 使用註解方式驗證
    能夠裝飾在屬性上,減小硬編碼量。

5) 支持JSR 303 – Bean Validation標準
    或許你已經使用了Hibernate Validator,不用拋棄它,FluentValidator能夠站在巨人的肩膀上。

6) Spring良好集成
    校驗器能夠由Spring IoC容器託管。校驗入參能夠直接使用註解,配置好攔截器,核心業務邏輯徹底沒有驗證邏輯的影子,乾淨利落。

7) 回調給予你充分的自由度
    驗證過程當中發生的錯誤、異常,驗證結果的返回,開發人員均可以定製。

 

4 哪裏能夠獲取到FluentValidator

項目託管在github上,地址點此https://github.com/neoremind/fluent-validator。說明文檔全英完成,i18n化,同時使用Apache2 License開源。

 

最新發布的Jar包能夠在maven中央倉庫找到,地址點此

 

5 上手

5.1 maven引入依賴

添加以下依賴到maven的pom.xml文件中:

<dependency>
    <groupId>com.baidu.unbiz</groupId>
    <artifactId>fluent-validator</artifactId>
    <version>1.0.5</version></dependency>

注:最新release請及時參考github

上面這個FluentValidator是個基礎核心包,只依賴於slf4j和log4j,若是你使用logback,想去掉log4j,排除掉的方法以下所示:

<dependency>
    <groupId>com.baidu.unbiz</groupId>
    <artifactId>fluent-validator</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions></dependency>

5.2 開發業務領域模型

從廣義角度來講DTO(Data Transfer Object)、VO(Value Object)、BO(Business Object)、POJO等均可以看作是業務表達模型。

咱們這裏建立一個汽車類(Car)的POJO,裏面定義了牌照(license plate)、座椅數(seat count)、生產商(manufacturer)。

public class Car {
    private String manufacturer;
    private String licensePlate;
    private int seatCount;     // getter and setter...}

5.3 開發一個專職的Validator

實際這裏須要開發三個Validator,分別對Car的3個屬性進行校驗,這裏以座椅數爲例展現如何開發一個Validator,其餘兩個省略。

public class CarSeatCountValidator extends ValidatorHandler<Integer> implements Validator<Integer> { 
    @Override    public boolean validate(ValidatorContext context, Integer t) {
        if (t < 2) {
            context.addErrorMsg(String.format("Seat count is not valid, invalid value=%s", t));
            return false;
        }
        return true;
    }}

很簡單,實現Validator接口,泛型T規範這個校驗器待驗證的對象的類型,繼承ValidatorHandler能夠避免實現一些默認的方法,例如accept(),後面會提到,validate()方法第一個參數是整個校驗過程的上下文,第二個參數是待驗證對象,也就是座椅數。

驗證邏輯很簡單,座椅數必須大於1,不然經過context放入錯誤消息而且返回false,成功返回true。

5.4 開始驗證吧

二話不說,直接上代碼:

Car car = getCar(); 
Result ret = FluentValidator.checkAll()
                            .on(car.getLicensePlate(), new CarLicensePlateValidator())
                            .on(car.getManufacturer(), new CarManufacturerValidator())
                            .on(car.getSeatCount(), new CarSeatCountValidator())
                            .doValidate()
                            .result(toSimple()); System.out.println(ret);

我想不用多說,若是你會英文,你就能知道這段代碼是如何工做的。這就是流式風格(Fluent Interface)調用的優雅之處,讓代碼更可讀、更好理解。

仍是稍微說明下,首先咱們經過FluentValidator.checkAll()獲取了一個FluentValidator實例,緊接着調用了failFast()表示有錯了當即返回,它的反義詞是failOver,而後,一連串on()操做表示在Car的3個屬性上依次使用3個校驗器進行校驗(這個過程叫作applying constraints),截止到此,真正的校驗還並無作,這就是所謂的「惰性求值(Lazy valuation)」,有點像Java8 Stream API中的filter()、map()方法,直到doValidate()驗證才真正執行了,最後咱們須要收殮出來一個結果供caller獲取打印,直接使用默認提供的靜態方法toSimple()來作一個回調函數傳入result()方法,最終返回Result類,若是座椅數不合法,那麼控制檯打印結果以下:

 

Result{isSuccess=false, errors=[Seat count is not valid, invalid value=99]}

 

 

6 深刻實踐

6.1 Validator詳解

Validator接口定義以下: 

public interface Validator<T> {  /**
 * 判斷在該對象上是否接受或者須要驗證
 * <p/>
 * 若是返回true,那麼則調用{@link #validate(ValidatorContext, Object)},不然跳過該驗證器
 *
 * @param context 驗證上下文
 * @param t 待驗證對象
 *
 * @return 是否接受驗證
 */
 boolean accept(ValidatorContext context, T t);  /**
 * 執行驗證
 * <p/>
 * 若是發生錯誤內部須要調用{@link ValidatorContext#addErrorMsg(String)}方法,也即<code>context.addErrorMsg(String)
 * </code>來添加錯誤,該錯誤會被添加到結果存根{@link Result}的錯誤消息列表中。
 *
 * @param context 驗證上下文
 * @param t 待驗證對象
 *
 * @return 是否驗證經過
 */
 boolean validate(ValidatorContext context, T t);  /**
 * 異常回調
 * <p/>
 * 當執行{@link #accept(ValidatorContext, Object)}或者{@link #validate(ValidatorContext, Object)}發生異常時的如何處理
 *
 * @param e 異常
 * @param context 驗證上下文
 * @param t 待驗證對象
 */
 void onException(Exception e, ValidatorContext context, T t); }

ValidatorHandler是實現Validator接口的一個模板類,若是你本身實現的Validator不想覆蓋上面3個方法,能夠繼承這個ValidatorHandler。

public class ValidatorHandler<T> implements Validator<T> { 
    @Override    public boolean accept(ValidatorContext context, T t) {
        return true;
    } 
    @Override    public boolean validate(ValidatorContext context, T t) {
        return true;
    } 
    @Override    public void onException(Exception e, ValidatorContext context, T t) {     }}

內部校驗邏輯發生錯誤時候,有兩個處理辦法,

第一,簡單處理,直接放入錯誤消息。

context.addErrorMsg("Something is wrong about the car seat count!");return false;

第二,須要詳細的信息,包括錯誤消息,錯誤屬性/字段,錯誤值,錯誤碼,均可以本身定義,放入錯誤的方法以下,create()方法傳入消息(必填),setErrorCode()方法設置錯誤碼(選填),setField()設置錯誤字段(選填),setInvalidValue()設置錯誤值(選填)。固然這些信息須要result(toComplex())才能夠獲取到,詳見6.7小節。

context.addError(ValidationError.create("Something is wrong about the car seat count!").setErrorCode(100).setField("seatCount").setInvalidValue(t));return false;

6.2 ValidatorChain

on()的一連串調用實際就是構建調用鏈,所以理所固然能夠傳入一個調用鏈。

ValidatorChain chain = new ValidatorChain();List<Validator> validators = new ArrayList<Validator>();validators.add(new CarValidator());chain.setValidators(validators); 
Result ret = FluentValidator.checkAll().on(car, chain).doValidate().result(toSimple());

6.3 onEach

若是要驗證的是一個集合(Collection)或者數組,那麼可使用onEach,FluentValidator會自動爲你遍歷,依次apply constraints。 

FluentValidator.checkAll()
               .onEach(Lists.newArrayList(new Car(), new Car()), new CarValidator());FluentValidator.checkAll()
               .onEach(new Car[]{}, new CarValidator());

6.4 fail fast or fail over

當出現校驗失敗時,也就是Validator的validate()方法返回了false,那麼是繼續仍是直接退出呢?默認爲使用failFast()方法,直接退出,若是你想繼續完成全部校驗,使用failOver()來skip掉。

FluentValidator.checkAll().failFast()
               .on(car.getManufacturer(), new CarManufacturerValidator());FluentValidator.checkAll().failOver()
               .on(car.getManufacturer(), new CarManufacturerValidator());

6.5 when

on()後面能夠緊跟一個when(),當when知足expression表達式on才啓用驗證,不然skip調用。

FluentValidator.checkAll()
               .on(car.getManufacturer(), new CarManufacturerValidator()).when(a == b)

6.6 驗證回調callback

doValidate()方法接受一個ValidateCallback接口,接口定義以下:

public interface ValidateCallback {  /**
 * 全部驗證完成而且成功後
 *
 * @param validatorElementList 驗證器list
 */
 void onSuccess(ValidatorElementList validatorElementList);  /**
 * 全部驗證步驟結束,發現驗證存在失敗後
 *
 * @param validatorElementList 驗證器list
 * @param errors 驗證過程當中發生的錯誤
 */
 void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors);  /**
 * 執行驗證過程當中發生了異常後
 *
 * @param validator 驗證器
 * @param e 異常
 * @param target 正在驗證的對象
 *
 * @throws Exception
 */
 void onUncaughtException(Validator validator, Exception e, Object target) throws Exception; }

默認的,使用不含參數的doValidate()方法,FluentValidator使用DefaultValidateCallback,其實現以下,能夠看出出錯了,成功了什麼也不作,有不可控異常的時候直接拋出。

public class DefaultValidateCallback implements ValidateCallback { 
    @Override    public void onSuccess(ValidatorElementList validatorElementList) {
    } 
    @Override    public void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors) {
    } 
    @Override    public void onUncaughtException(Validator validator, Exception e, Object target) throws Exception {
        throw e;
    }}

若是你不滿意這種方式,例如成功的時候打印一些消息,一種實現方式以下:

Result ret = FluentValidator.checkAll()
                            .on(car.getSeatCount(), new CarSeatCountValidator())
                            .doValidate(new DefaulValidateCallback() { 
                                @Override                                public void onSuccess(ValidatorElementList validatorElementList) {
                                    LOG.info("all ok!");
                                }
                            }).result(toSimple());

6.7 獲取結果

result()接受一個ResultCollector接口,如上面所示,toSimple()實際是個靜態方法,這種方式在Java8 Stream API中很常見,默承認以使用FluentValidator自帶的簡單結果Result,若是須要可使用複雜ComplexResult,內含錯誤消息,錯誤屬性/字段,錯誤值,錯誤碼,以下所示:

ComplexResult ret = FluentValidator.checkAll().failOver()
                                   .on(company, new CompanyCustomValidator())
                                   .doValidate().result(toComplex());

固然,若是你想本身實現一個結果類型,徹底能夠定製,實現ResultCollector接口便可。


public interface ResultCollector<T> {  /**
 * 轉換爲對外結果
 *
 * @param result 框架內部驗證結果
 *
 * @return 對外驗證結果對象
 */
 T toResult(ValidationResult result);}


toSimple() 和 toComplex()方法是經過以下方式static import進來的:

import static com.baidu.unbiz.fluentvalidator.ResultCollectors.toSimple;

import static com.baidu.unbiz.fluentvalidator.ResultCollectors. toComplex;

6.8 關於RuntimeValidateException

若是驗證中發生了一些不可控異常,例如數據庫調用失敗,RPC鏈接失效等,會拋出一些異常,若是Validator沒有try-catch處理,FluentValidator會將這些異常封裝在RuntimeValidateException,而後再re-throw出去,這個狀況你應該知曉並做出你認爲最正確的處理。 

6.9 context上下文共享

經過putAttribute2Context()方法,能夠往FluentValidator注入一些鍵值對,在全部Validator中共享,有時候這至關有用。

例以下面展現了在caller添加一個ignoreManufacturer屬性,而後再Validator中拿到這個值的過程。

FluentValidator.checkAll()
               .putAttribute2Context("ignoreManufacturer", true)
               .on(car.getManufacturer(), new CarManufacturerValidator())
               .doValidate().result(toSimple()); public class CarManufacturerValidator extends ValidatorHandler<String> implements Validator<String> { 
    @Override    public boolean validate(ValidatorContext context, String t) {
        Boolean ignoreManufacturer = context.getAttribute("ignoreManufacturer", Boolean.class);
        if (ignoreManufacturer != null && ignoreManufacturer) {
            return true;
        }
        // ...
    } }

6.10 閉包

經過putClosure2Context()方法,能夠往FluentValidator注入一個閉包,這個閉包的做用是在Validator內部能夠調用,而且緩存結果到Closure中,這樣caller在上層能夠獲取這個結果。

典型的應用場景是,當須要頻繁調用一個RPC的時候,每每該執行線程內部一次調用就夠了,屢次調用會影響性能,咱們就能夠緩存住這個結果,在全部Validator間和caller中共享。

下面展現了在caller處存在一個manufacturerService,它假如須要RPC才能獲取全部生產商,顯然是很耗時的,能夠在validator中調用,而後validator內部共享的同時,caller能夠利用閉包拿到結果,用於後續的業務邏輯。

 

Closure<List<String>> closure = new ClosureHandler<List<String>>() {     private List<String> allManufacturers; 
    @Override    public List<String> getResult() {
        return allManufacturers;
    } 
    @Override    public void doExecute(Object... input) {
        allManufacturers = manufacturerService.getAllManufacturers();
    }}; 
Result ret = FluentValidator.checkAll()
                            .putClosure2Context("manufacturerClosure", closure)
                            .on(car, validator)
                            .doValidate().result(toSimple()); 
 public class CarValidator extends ValidatorHandler<Car> implements Validator<Car> { 
    @Override    public boolean validate(ValidatorContext context, Car car) {
        Closure<List<String>> closure = context.getClosure("manufacturerClosure");
        List<String> manufacturers = closure.executeAndGetResult();         if (!manufacturers.contains(car.getManufacturer())) {
            context.addErrorMsg(String.format(CarError.MANUFACTURER_ERROR.msg(), car.getManufacturer()));
            return false;
        }         return true;
    }}

 

 

7 高級玩法

7.1 與JSR303規範最佳實現Hibernate Validator集成

Hibernate ValidatorJSR 303 – Bean Validation規範的一個最佳的實現類庫,他僅僅是jboss家族的一員,和大名鼎鼎的Hibernate ORM是系出同門,屬於遠房親戚關係。不少框架都會自然集成這個優秀類庫,例如Spring MVC的@Valid註解能夠爲Controller方法上的參數作校驗。

FluentValidator固然不會重複早輪子,這麼好的類庫,必定要使用站在巨人肩膀上的策略,將它集成進來。

想要了解更多Hibernate Validator用法,參考這個連接

下面的例子展現了@NotEmpty, @Pattern, @NotNull, @Size, @Length和@Valid這些註解裝飾一個公司類(Company)以及其成員變量(Department)。

public class Company {
    @NotEmpty
    @Pattern(regexp = "[0-9a-zA-Z\4e00-\u9fa5]+")
    private String name; 
    @NotNull(message = "The establishTime must not be null")
    private Date establishTime; 
    @NotNull
    @Size(min = 0, max = 10)
    @Valid    private List<Department> departmentList;     // getter and setter...} public class Department {
    @NotNull    private Integer id; 
    @Length(max = 30)
    private String name;     // getter and setter...}

咱們要在這個Company類上作校驗,首先引入以下的jar包到pom.xml中。

<dependency>
    <groupId>com.baidu.unbiz</groupId>
    <artifactId>fluent-validator-jsr303</artifactId>
    <version>1.0.5</version></dependency>

默認依賴於Hibernate Validator 5.2.1.Final版本。

而後咱們使用HibernateSupportedValidator這個驗證器來在company上作校驗,它和普通的Validator沒任何區別。

Result ret = FluentValidator.checkAll()
                            .on(company, new HibernateSupportedValidator<Company>().setValidator(validator))
                            .on(company, new CompanyCustomValidator())
                            .doValidate().result(toSimple());System.out.println(ret);

注意這裏的HibernateSupportedValidator依賴於javax.validation.Validator的實現,也就是Hibernate Validator,不然它將不會正常工做。

下面是Hibernate Validator官方提供的初始化javax.validation.Validator實現的方法,供參考使用。 

Locale.setDefault(Locale.ENGLISH); // speicify languageValidatorFactory factory = Validation.buildDefaultValidatorFactory();javax.validation.Validator validator = factory.getValidator();

7.2 註解驗證

一直以來介紹的方式都是經過顯示的API調用來進行驗證,FluentValidator一樣提供簡潔的基於註解配置的方式來達到一樣的效果。

@FluentValidate能夠裝飾在屬性上,內部接收一個Class[]數組參數,這些個classes必須是Validator的子類,這叫代表在某個屬性上依次用這些Validator作驗證。舉例以下,咱們改造下Car這個類: 

public class Car { 
    @FluentValidate({CarManufacturerValidator.class})
    private String manufacturer; 
    @FluentValidate({CarLicensePlateValidator.class})
    private String licensePlate; 
    @FluentValidate({CarSeatCountValidator.class})
    private int seatCount;     // getter and setter ... }

而後仍是利用on()或者onEach()方法來校驗,這裏只不過不用傳入Validator或者ValidatorChain了。 

Car car = getCar(); 
Result ret = FluentValidator.checkAll().configure(new SimpleRegistry())
                            .on(car)
                            .doValidate()
                            .result(toSimple());

那麼問題來了?不要問我挖掘機(Validator)哪家強,先來講挖掘(Validator)從哪來。

默認的,FluentValidator使用SimpleRegistry,它會嘗試從當前的class loader中調用Class.newInstance()方法來新建一個Validator,具體使用示例以下,默認的你不須要使用configure(new SimpleRegistry())。

通常狀況下,SimpleRegistry都夠用了。除非你的驗證器須要Spring IoC容器管理的bean注入,那麼你乾脆能夠把Validator也用Spring託管,使用@Service或者@Component註解在Validator類上就能夠作到,以下所示: 

@Componentpublic class CarSeatCountValidator extends ValidatorHandler<Integer> implements Validator<Integer> {
    // ...}

這時候,須要使用SpringApplicationContextRegistry,它的做用就是去Spring的容器中尋找Validator。想要使用Spring加強功能,將下面的maven依賴加入到pom.xml中。

<dependency>
    <groupId>com.baidu.unbiz</groupId>
    <artifactId>fluent-validator-spring</artifactId>
    <version>1.0.5</version></dependency>

依賴樹以下,請自覺排除或者更換不想要的版本。 

[INFO] +- org.springframework:spring-context-support:jar:4.1.6.RELEASE:compile[INFO] | +- org.springframework:spring-beans:jar:4.1.6.RELEASE:compile[INFO] | +- org.springframework:spring-context:jar:4.1.6.RELEASE:compile[INFO] | | +- org.springframework:spring-aop:jar:4.1.6.RELEASE:compile[INFO] | | | \- aopalliance:aopalliance:jar:1.0:compile[INFO] | | \- org.springframework:spring-expression:jar:4.1.6.RELEASE:compile[INFO] | \- org.springframework:spring-core:jar:4.1.6.RELEASE:compile[INFO] +- org.slf4j:slf4j-api:jar:1.7.7:compile[INFO] +- org.slf4j:slf4j-log4j12:jar:1.7.7:compile[INFO] | \- log4j:log4j:jar:1.2.17:compile

讓Spring容器去管理SpringApplicationContextRegistry,因爲這個類須要實現ApplicationContextAware接口,因此Spring會幫助咱們把context注入到裏面,咱們也就能夠隨意找bean了。

調用的例子省略,原來怎麼校驗如今還怎麼來。

7.3 分組

當使用註解驗證時候,會遇到這樣的狀況,某些時候例如添加操做,咱們會驗證A/B/C三個屬性,而修改操做,咱們須要驗證B/C/D/E 4個屬性,顯示調用FluentValidator API的狀況下,咱們能夠作到「想驗證什麼,就驗證什麼」。

@FluentValidate註解另一個接受的參數是groups,裏面也是Class[]數組,只不過這個Class能夠是開發人員隨意寫的一個簡單的類,不含有任何屬性方法均可以,例如:

public class Add {} public class Car { 
    @FluentValidate(value = {CarManufacturerValidator.class}, groups = {Add.class})
    private String manufacturer; }

那麼驗證的時候,只須要在checkAll()方法中傳入想要驗證的group,就只會作選擇性的分組驗證,例以下面例子,只有生產商會被驗證。

Result ret = FluentValidator.checkAll(new Class<?>[] {Add.class})
                            .on(car)
                            .doValidate()
                            .result(toSimple());

這種groups的方法一樣適用於hibernate validator,想了解更多請參考官方文檔。

7.4 級聯對象圖

級聯驗證(cascade validation),也叫作對象圖(object graphs),指一個類嵌套另一個類的時候作的驗證。

以下例所示,咱們在車庫(Garage)類中含有一個汽車列表(carList),能夠在這個汽車列表屬性上使用@FluentValid註解,表示須要級聯到內部Car作onEach驗證。 

public class Garage { 
    @FluentValidate({CarNotExceedLimitValidator.class})
    @FluentValid    private List<Car> carList;}

注意,@FluentValid和@FluentValidate兩個註解不互相沖突,以下所示,調用鏈會先驗證carList上的CarNotExceedLimitValidator,而後再遍歷carList,對每一個car作內部的生產商、座椅數、牌照驗證。

7.5 Spring AOP集成

讀到這裏,你會發現,只介紹了優雅,說好的業務邏輯和驗證邏輯解耦合呢?這須要藉助Spring AOP技術實現,以下示例,addCar()方法內部上來就是真正的核心業務邏輯,注意參數上的car前面裝飾有@FluentValid註解,這表示須要Spring利用切面攔截方法,對參數利用FluentValidator作校驗。 

@Servicepublic class CarServiceImpl implements CarService { 
    @Override    public Car addCar(@FluentValid Car car) {
        System.out.println("Come on! " + car);
        return car;
    }}

Spring XML配置以下,首先定義了ValidateCallback表示該如何處理成功,失敗,拋出不可控異常三種狀況,所以,即便你拿不到Result結果,也能夠作本身的操做,例以下面例子,若是發生錯誤,拋出CarException,而且消息是第一條錯誤信息,若是成功則打印"Everything works fine",若是失敗,把實際的異常e放入運行時的CarException拋出。

public class ValidateCarCallback extends DefaulValidateCallback implements ValidateCallback { 
    @Override    public void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors) {
        throw new CarException(errors.get(0).getErrorMsg());
    } 
    @Override    public void onSuccess(ValidatorElementList validatorElementList) {
        System.out.println("Everything works fine!");
    } 
    @Override    public void onUncaughtException(Validator validator, Exception e, Object target) throws Exception {
        throw new CarException(e);
    }}

將ValidateCarCallback注入到FluentValidateInterceptor中。而後利用BeanNameAutoProxyCreator在*ServiceImpl上用fluentValidateInterceptor攔截作驗證,這有點相似Spring事務處理攔截器的使用方式。更多AOP的用法請參考Spring官方文檔

<bean id="validateCarCallback" class="com.baidu.unbiz.fluentvalidator.interceptor.ValidateCarCallback"/> <bean id="fluentValidateInterceptor"
 class="com.baidu.unbiz.fluentvalidator.interceptor.FluentValidateInterceptor">
    <property name="callback" ref="validateCarCallback"/>
    <property name="locale" value="zh_CN"/>
    <property name="hibernateDefaultErrorCode" value="10000"/></bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>
            <value>*ServiceImpl</value>
        </list>
    </property>
    <property name="interceptorNames">
        <list>
            <value>fluentValidateInterceptor</value>
        </list>
    </property></bean>

測試代碼以下:

@Testpublic void testAddCarNegative() {
    try {
        Car car = getValidCar();
        car.setLicensePlate("BEIJING123");
        carService.addCar(car);
    } catch (CarException e) {
        assertThat(e.getMessage(), Matchers.is("License is not valid, invalid value=BEIJING123"));
        return;
    }
    fail();}

7.6 國際化支持

也就是常說的i18n,使用Spring提供的MessageSource來支持,只須要再Spring的XML配置中加入以下配置:

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
    <list>
        <value>error-message</value>
    </list>
    </property></bean>

 同時編輯兩個國際化文件,路徑放在classpath中便可,一般放到resource下面,文件名分別叫作error-message.properties和error-message_zh_CN.properties表示默認(英文)和中文-簡體中文消息。

car.size.exceed=Car number exceeds limit, max available num is {0}
car.size.exceed=\u6c7d\u8f66\u6570\u91cf\u8d85\u8fc7\u9650\u5236\uff0c\u6700\u591a\u5141\u8bb8{0}\u4e2a

 注意,其中{0}表明和運行時填充的參數。

在Validator使用時,示例以下,只用MessageSupport.getText(..)來獲取國際化信息。

public class GarageCarNotExceedLimitValidator extends ValidatorHandler&lt;Garage&gt; implements Validator&lt;Garage&gt; {  public static final int MAX_CAR_NUM = 50; 
 @Override public boolean validate(ValidatorContext context, Garage t) {
    if (!CollectionUtil.isEmpty(t.getCarList())) {
        if (t.getCarList().size() &gt; MAX_CAR_NUM) {
            context.addError(
 ValidationError.create(MessageSupport.getText(GarageError.CAR_NUM_EXCEED_LIMIT.msg(),
            MAX_CAR_NUM))
           .setErrorCode(GarageError.CAR_NUM_EXCEED_LIMIT.code())
           .setField(&quot;garage.cars&quot;)
           .setInvalidValue(t.getCarList().size()));
        return false;
        }
    }
    return true;
 } }

這裏須要注意下,若是使用HibernateValidator打的註解,例如@NotNull,@Length等,須要將錯誤信息放到ValidationMessages.properties和ValidationMessages_zh_CN.properties中,不然找不到哦。

8 總結

上面對FluentValidator作了一個全面的介紹,從顯示的流式風格(Fluent Interface)API調用,以及各類豐富多樣的鏈操做方法,再到對JSR303 – Bean Validation規範的集成,最後介紹了高級點的註解方式驗證、支持級聯對象圖和Spring AOP的集成,我想這完成了對FluentValidator任務的詮釋,更好的幫助開發人員作業務邏輯驗證,但願能給你的項目一點幫助,哪怕不是用這個框架,而只是汲取一些思想,而後產生本身的思考,讓咱們的clean code更加「環保」。

相關文章
相關標籤/搜索