Fluent-Validator 業(yè)務(wù)校驗器

Fluent-Validator 業(yè)務(wù)校驗器

背景

在互聯(lián)網(wǎng)行業(yè)中嫡意,基于Java開發(fā)的業(yè)務(wù)類系統(tǒng),不管是服務(wù)端還是客戶端烹看,業(yè)務(wù)邏輯代碼的更新往往是非常頻繁的,這源于功能的快速迭代特性固耘。在一般公司內(nèi)部,特別是使用Java web技術(shù)構(gòu)建的平臺中,不管是基于模塊化還是服務(wù)化的纵诞,業(yè)務(wù)邏輯都會相對復(fù)雜。
這些系統(tǒng)之間器予、系統(tǒng)內(nèi)部往往存在大量的API接口浪藻,這些接口一般都需要對入?yún)ⅲㄝ斎雲(yún)?shù)的簡稱)做校驗,以保證:
1) 核心業(yè)務(wù)邏輯能夠順利按照預(yù)期執(zhí)行乾翔。
2) 數(shù)據(jù)能夠正常存取爱葵。
3) 數(shù)據(jù)安全性。包括符合約束以及限制反浓,有訪問權(quán)限控制以及不出現(xiàn)SQL注入等問題萌丈。
開發(fā)人員在維護(hù)核心業(yè)務(wù)邏輯的同時,還需要為輸入做嚴(yán)格的校驗雷则。當(dāng)輸入不合法時辆雾,能夠給caller一個明確的反饋,最常見的反饋就是返回封裝了result的對象或者拋出exception月劈。
一些常見的驗證代碼片段如下所示:

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
}

我們可以發(fā)現(xiàn)度迂,它不夠優(yōu)雅而且違反一些范式:
1)違反單一職責(zé)原則(Single responsibility)。核心業(yè)務(wù)邏輯(core business logic)和驗證邏輯(validation logic)耦合在一個類中猜揪。
2)開閉原則(Open/closed)惭墓。我們應(yīng)該對擴展開放,對修改封閉而姐,驗證邏輯不好擴展腊凶,而且一旦需要修改需要動整體這個類。
3)DRY原則(Don’t repeat yourself)拴念。代碼冗余钧萍,相同邏輯可能散落多處,長此以往不好收殮丈莺。

1.簡介

FluentValidato是一個適用于以Java語言開發(fā)的程序划煮,讓開發(fā)人員回歸focus到業(yè)務(wù)邏輯上,使用流式(Fluent Interface)調(diào)用風(fēng)格讓驗證跑起來很優(yōu)雅缔俄,同時驗證器(Validator)可以做到開閉原則弛秋,實現(xiàn)最大程度的復(fù)用的工具庫器躏。

2.特點

  1. 驗證邏輯與業(yè)務(wù)邏輯不再耦合
    摒棄原來不規(guī)范的驗證邏輯散落的現(xiàn)象。
  2. 校驗器各司其職蟹略,好維護(hù)登失,可復(fù)用,可擴展
    一個校驗器(Validator)只負(fù)責(zé)某個屬性或者對象的校驗挖炬,可以做到職責(zé)單一揽浙,易于維護(hù),并且可復(fù)用意敛。
  3. 流式風(fēng)格(Fluent Interface)調(diào)用
  4. 使用注解方式驗證
    可以裝飾在屬性上馅巷,減少硬編碼量。
  5. 支持JSR 303 – Bean Validation標(biāo)準(zhǔn)
    或許你已經(jīng)使用了Hibernate Validator草姻,不用拋棄它钓猬,F(xiàn)luentValidator可以站在巨人的肩膀上。
  6. Spring良好集成
    校驗器可以由Spring IoC容器托管撩独。校驗入?yún)⒖梢灾苯邮褂米⒔獬ú埽渲煤脭r截器,核心業(yè)務(wù)邏輯完全沒有驗證邏輯的影子综膀,干凈利落澳迫。
  7. 回調(diào)給予你充分的自由度
    驗證過程中發(fā)生的錯誤、異常剧劝,驗證結(jié)果的返回橄登,開發(fā)人員都可以定制。

3.上手

3.1引入maven依賴:

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

3.2 業(yè)務(wù)領(lǐng)域模型

從廣義角度來說DTO(Data Transfer Object)担平、VO(Value Object)示绊、BO(Business Object)、POJO等都可以看做是業(yè)務(wù)表達(dá)模型暂论。
創(chuàng)建一個學(xué)生類面褐,包含 name(姓名)、age(年齡)取胎、schoolName(學(xué)校名稱)展哭、(area)地區(qū)

package com.example.fluentvalidator;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @author :jianyul
 * @date : 2022/5/16 18:00
 */
@Data
@AllArgsConstructor
public class StudentDto {

    private String name;

    private Integer age;

    private String schoolName;

    private String area;
}

3.3 Validator樣例

針對schoolName(學(xué)校名稱)創(chuàng)建一個Validator,代碼如下:

public class SchoolNameValidator extends ValidatorHandler<String> implements Validator<String> {
    @Override
    public boolean validate(ValidatorContext context, String schoolName) {
        if (!"無錫中學(xué)".equals(schoolName)) {
            context.addErrorMsg("學(xué)校名稱不正確");
            return false;
        }
        return true;
    }
}

很簡單闻蛀,實現(xiàn)Validator接口匪傍,泛型T規(guī)范這個校驗器待驗證的對象的類型,繼承ValidatorHandler可以避免實現(xiàn)一些默認(rèn)的方法觉痛,validate()方法第一個參數(shù)是整個校驗過程的上下文役衡,第二個參數(shù)是待驗證對象,也就是學(xué)校名稱薪棒。
驗證邏輯:假設(shè)學(xué)校名稱必須是無錫中學(xué)手蝎,否則通過context放入錯誤消息并且返回false榕莺,成功返回true。

3.4 驗證

   StudentDto studentDto = new StudentDto("張三", 18, "蘇州中學(xué)", "無錫");
                Result result =
                        FluentValidator.checkAll()
                                .on(studentDto.getSchoolName(), new SchoolNameValidator())
                                .doValidate()
                                .result(toSimple());
                System.out.println(result);
 //打印結(jié)果:Result{isSuccess=false, errors=[學(xué)校名稱不正確]}

首先我們通過FluentValidator.checkAll()獲取了一個FluentValidator實例棵介,緊接著調(diào)用了failFast()表示有錯了立即返回钉鸯,它的反義詞是failOver,然后邮辽,唠雕、on()操作表示在指定屬性上使用對應(yīng)校驗器進(jìn)行校驗,截止到此吨述,真正的校驗還并沒有做岩睁,這就是所謂的“惰性求值(Lazy valuation)”,有點像Java8 Stream API中的filter()揣云、map()方法笙僚,直到doValidate()驗證才真正執(zhí)行了,最后我們需要收殮出來一個結(jié)果供caller獲取打印灵再,直接使用默認(rèn)提供的靜態(tài)方法toSimple()來做一個回調(diào)函數(shù)傳入result()方法,最終返回Result類亿笤。

4.深入了解

4.1 Validator詳解

Validator接口代碼如下:

public interface Validator<T> {
 
 /**
 * 判斷在該對象上是否接受或者需要驗證
 * <p/>
 * 如果返回true翎迁,那么則調(diào)用{@link #validate(ValidatorContext, Object)},否則跳過該驗證器
 *
 * @param context 驗證上下文
 * @param t 待驗證對象
 *
 * @return 是否接受驗證
 */
 boolean accept(ValidatorContext context, T t);
 
 /**
 * 執(zhí)行驗證
 * <p/>
 * 如果發(fā)生錯誤內(nèi)部需要調(diào)用{@link ValidatorContext#addErrorMsg(String)}方法净薛,也即<code>context.addErrorMsg(String)
 * </code>來添加錯誤汪榔,該錯誤會被添加到結(jié)果存根{@link Result}的錯誤消息列表中。
 *
 * @param context 驗證上下文
 * @param t 待驗證對象
 *
 * @return 是否驗證通過
 */
 boolean validate(ValidatorContext context, T t);
 
 /**
 * 異乘喟荩回調(diào)
 * <p/>
 * 當(dāng)執(zhí)行{@link #accept(ValidatorContext, Object)}或者{@link #validate(ValidatorContext, Object)}發(fā)生異常時的如何處理
 *
 * @param e 異常
 * @param context 驗證上下文
 * @param t 待驗證對象
 */
 void onException(Exception e, ValidatorContext context, T t);
 
}

ValidatorHandler是實現(xiàn)Validator接口的一個模板類痴腌,如果你自己實現(xiàn)的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) {
 
    }
}

內(nèi)部校驗邏輯發(fā)生錯誤時候士聪,有兩個處理辦法,
第一猛蔽,簡單處理剥悟,如上述3.3中代碼所示:
context.addErrorMsg("學(xué)校名稱不正確");
第二,需要詳細(xì)的信息曼库,包括錯誤消息区岗,錯誤屬性/字段,錯誤值毁枯,錯誤碼慈缔,都可以自己定義,放入錯誤的方法如下种玛,create()方法傳入消息(必填)藐鹤,setErrorCode()方法設(shè)置錯誤碼(選填)瓤檐,setField()設(shè)置錯誤字段(選填),setInvalidValue()設(shè)置錯誤值(選填)教藻。當(dāng)然這些信息需要result(toComplex())才可以獲取到距帅。

public class AreaValidator extends ValidatorHandler<String> implements Validator<String> {
    // 實現(xiàn)Validator接口,泛型T規(guī)范這個校驗器待驗證的對象的類型
    @Override
    public boolean validate(ValidatorContext context, String area) {
        if (!"無錫".equals(area)) {
            context.addError(
                    ValidationError.create("地址不正確")
                            .setErrorCode(5000)
                            .setField("area")
                            .setInvalidValue(area));
            // context.addErrorMsg("地址不正確");
            return false;
        }
        return true;
    }
}

如果需要可以使用復(fù)雜ComplexResult括堤,內(nèi)含錯誤消息碌秸,錯誤屬性/字段,錯誤值悄窃,錯誤碼讥电,如下所示:

ComplexResult ret =
        FluentValidator.checkAll()
                .failOver()
                .on(studentDto.getArea(), new AreaValidator())
                .doValidate()
                .result(toComplex());
System.out.println(ret);
//打印結(jié)果:Result{isSuccess=false, errors=[ValidationError{errorCode=5000, errorMsg='地址不正確', field='area', invalidValue=蘇州}], timeElapsedInMillis=1}

上述都是針對單個屬性值編寫對應(yīng)的Validator代碼,實際開發(fā)中轧抗,我們需要對整個對象的多個屬性進(jìn)行業(yè)務(wù)校驗恩敌,這時我們可以針對整個對象編寫對應(yīng)的Validator,最后用ComplexResult來接收校驗結(jié)果横媚,代碼如下:

public class StudentValidator extends ValidatorHandler<StudentDto>
        implements Validator<StudentDto> {
    @Override
    public boolean validate(ValidatorContext context, StudentDto studentDto) {
        if (!"無錫".equals(studentDto.getArea())) {
            context.addError(
                    ValidationError.create("地址不正確")
                            .setErrorCode(5000)
                            .setField("area")
                            .setInvalidValue(studentDto.getArea()));
        }
        if (!"無錫中學(xué)".equals(studentDto.getSchoolName())) {
            context.addError(
                    ValidationError.create("學(xué)校名稱不正確")
                            .setErrorCode(5000)
                            .setField("schoolName")
                            .setInvalidValue(studentDto.getSchoolName()));
        }
        //校驗有沒有Error信息
        if (CollectionUtils.isNotEmpty(context.result.getErrors())) {
            return false;
        }
        return true;
    }
}

on()的一連串調(diào)用實際就是構(gòu)建調(diào)用鏈纠炮,因此理所當(dāng)然可以傳入一個調(diào)用鏈。

   ValidatorChain chain = new ValidatorChain();
        List<Validator> validators = new ArrayList<Validator>();
        validators.add(new StudentValidator());
        chain.setValidators(validators);
        ComplexResult rets =
                FluentValidator.checkAll().on(studentDto, chain).doValidate().result(toComplex());
        System.out.println(rets);

//打印結(jié)果:Result{isSuccess=false, errors=[ValidationError{errorCode=5000, errorMsg='地址不正確', field='area', invalidValue=蘇州}, ValidationError{errorCode=5000, errorMsg='學(xué)校名稱不正確', field='schoolName', invalidValue=蘇州中學(xué)}], timeElapsedInMillis=7}

拓展

可根據(jù)項目自定義返回結(jié)果類型灯蝴,實現(xiàn)ResultCollector即可

public interface ResultCollector<T> {
 
 /**
 * 轉(zhuǎn)換為對外結(jié)果
 *
 * @param result 框架內(nèi)部驗證結(jié)果
 *
 * @return 對外驗證結(jié)果對象
 */
 T toResult(ValidationResult result);
}

4.2 onEach

如果要驗證的是一個集合(Collection)或者數(shù)組恢口,那么可以使用onEach,F(xiàn)luentValidator會自動為你遍歷:

    ComplexResult result =
                FluentValidator.checkAll()
                        .onEach(list, new StudentValidator())
                        .doValidate()
                        .result(toComplex());

4.3 fail fast or fail over

當(dāng)出現(xiàn)校驗失敗時穷躁,也就是Validator的validate()方法返回了false耕肩,那么是繼續(xù)還是直接退出呢?默認(rèn)為使用failFast()方法问潭,直接退出猿诸,如果你想繼續(xù)完成所有校驗,使用failOver()來skip掉狡忙。

  ComplexResult result1 =
                FluentValidator.checkAll()
                        .failFast()
                        .on(liS.getArea(), new AreaValidator())
                        .on(liS.getSchoolName(), new SchoolNameValidator())
                        .doValidate()
                        .result(toComplex());

  ComplexResult result2 =
                FluentValidator.checkAll()
                        .failOver()
                        .on(liS.getArea(), new AreaValidator())
                        .on(liS.getSchoolName(), new SchoolNameValidator())
                        .doValidate()
                        .result(toComplex());

4.4 when

on()后面可以緊跟一個when()梳虽,當(dāng)when滿足expression表達(dá)式on才啟用驗證,否則skip調(diào)用灾茁。

  ComplexResult result =
                FluentValidator.checkAll()
                        .failOver()
                        .on(liS.getArea(), new AreaValidator())
                        .when("20".equals(liS.getAge()))
                        .on(liS.getSchoolName(), new SchoolNameValidator())
                        .doValidate()
                        .result(toComplex());
        System.out.println(result);

4.5 驗證回調(diào)callBack

doValidate()方法接受一個ValidateCallback接口

public interface ValidateCallback {
 
 /**
 * 所有驗證完成并且成功后
 *
 * @param validatorElementList 驗證器list
 */
 void onSuccess(ValidatorElementList validatorElementList);
 
 /**
 * 所有驗證步驟結(jié)束怖辆,發(fā)現(xiàn)驗證存在失敗后
 *
 * @param validatorElementList 驗證器list
 * @param errors 驗證過程中發(fā)生的錯誤
 */
 void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors);
 
 /**
 * 執(zhí)行驗證過程中發(fā)生了異常后
 *
 * @param validator 驗證器
 * @param e 異常
 * @param target 正在驗證的對象
 *
 * @throws Exception
 */
 void onUncaughtException(Validator validator, Exception e, Object target) throws Exception;
 
}

我們可以根據(jù)業(yè)務(wù)需求,在校驗回調(diào)接口中做其他邏輯處理删顶,如下所示:

 FluentValidator.checkAll()
                        .on(liS.getSchoolName(), new SchoolNameValidator())
                        .doValidate(
                                new DefaultValidateCallback() {
                                    @Override
                                    public void onSuccess(
                                            ValidatorElementList validatorElementList) {
                                        
                                        System.out.println("校驗成功");
                                    }

                                    @Override
                                    public void onFail(
                                            ValidatorElementList validatorElementList,
                                            List<ValidationError> errors) {
                                        System.out.println("校驗失敗");
                                    }
                                })
                        .result(toComplex());

4.6 RuntimeValidateException

如果驗證中發(fā)生了一些不可控異常竖螃,例如數(shù)據(jù)庫調(diào)用失敗,RPC連接失效等逗余,會拋出一些異常特咆,如果Validator沒有try-catch處理,F(xiàn)luentValidator會將這些異常封裝在RuntimeValidateException,然后再re-throw出去腻格。

4.7 上下文傳遞

通過putAttribute2Context()方法画拾,可以往FluentValidator注入一些鍵值對,在所有Validator中共享菜职,有時候這相當(dāng)有用青抛。

 Result result =
                FluentValidator.checkAll()
                        .putAttribute2Context("school", "常州中學(xué)")
                        .on(liS.getSchoolName(), new SchoolNameValidator())
                        .doValidate()
                        .result(toSimple());

可在Validator中通過context.getAttribute拿到這個值

   String name = context.getAttribute("school", String.class);
        if (!name.equals(schoolName)) {
            context.addErrorMsg("學(xué)校名稱不正確");
            return false;
        }
        return true;

4.8 閉包

通過putClosure2Context()方法,可以往FluentValidator注入一個閉包酬核,這個閉包的作用是在Validator內(nèi)部可以調(diào)用蜜另,并且緩存結(jié)果到Closure中,這樣caller在上層可以獲取這個結(jié)果嫡意。
典型的應(yīng)用場景是举瑰,當(dāng)需要頻繁調(diào)用一個RPC的時候,往往該執(zhí)行線程內(nèi)部一次調(diào)用就夠了蔬螟,多次調(diào)用會影響性能此迅,我們就可以緩存住這個結(jié)果,在所有Validator間和caller中共享旧巾。
下面展示了在caller處存在一個getAreas()方法耸序,它假如需要RPC才能獲取所有地區(qū)信息,顯然是很耗時的鲁猩,可以在validator中調(diào)用佑吝,然后validator內(nèi)部共享的同時,caller可以利用閉包拿到結(jié)果绳匀,用于后續(xù)的業(yè)務(wù)邏輯。

  StudentDto liS = new StudentDto("李四", 18, "常州中學(xué)", "常州");
        Closure<List<String>> closure =
                new ClosureHandler<List<String>>() {

                    private List<String> allManufacturers;

                    @Override
                    public List<String> getResult() {
                        return allManufacturers;
                    }

                    @SneakyThrows
                    @Override
                    public void doExecute(Object... input) {
                        // getAreas()模擬RPC遠(yuǎn)程接口調(diào)用
                        allManufacturers = getAreas();
                    }
                };
        FluentValidator.checkAll()
                .putClosure2Context("area", closure)
                .on(liS.getArea(), new AreaValidator())
                .doValidate()
                .result(toSimple());

Validator中獲取接口查詢的數(shù)據(jù):

  Closure<List<String>> closure = context.getClosure("area");
        List<String> areas = closure.executeAndGetResult();
        if (!areas.contains(area)) {
            context.addError(
                    ValidationError.create("地址不正確")
                            .setErrorCode(5000)
                            .setField("area")
                            .setInvalidValue(area));
            return false;
        }
        return true;
    }

5.高級玩法

Hibernate Validator集成

Hibernate ValidatorJSR 303 – Bean Validation規(guī)范的一個最佳的實現(xiàn)類庫炸客,他僅僅是jboss家族的一員疾棵,和大名鼎鼎的Hibernate ORM是系出同門,屬于遠(yuǎn)房親戚關(guān)系痹仙。很多框架都會天然集成這個優(yōu)秀類庫是尔,例如Spring MVC的@Valid注解可以為Controller方法上的參數(shù)做校驗。
FluentValidator當(dāng)然不會重復(fù)早輪子开仰,這么好的類庫拟枚,一定要使用站在巨人肩膀上的策略,將它集成進(jìn)來众弓。
想要了解更多Hibernate Validator用法恩溅,參考這個鏈接
fluent-validator 集成 hibernate-validator 需要添加依賴

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

5.1 注解驗證

上述都是通過顯示的API調(diào)用來進(jìn)行驗證谓娃,F(xiàn)luentValidator同樣提供簡潔的基于注解配置的方式來達(dá)到同樣的效果脚乡。
@FluentValidate可以裝飾在屬性上,內(nèi)部接收一個Class[]數(shù)組參數(shù)滨达,這些個classes必須是Validator的子類奶稠,這叫表明在某個屬性上依次用這些Validator做驗證俯艰。如下,我們改造下StudentDto這個類:

@Data
@AllArgsConstructor
public class StudentDto {

    @NotNull private String name;

    private Integer age;

    @Length(max = 5)
    @FluentValidate({SchoolNameValidator.class})
    private String schoolName;

    @FluentValidate({AreaValidator.class})
    private String area;
}

然后還是利用on()或者onEach()方法來校驗锌订,這里只不過不用傳入Validator或者ValidatorChain了竹握。

       ComplexResult ret =
                FluentValidator.checkAll()
                        .failOver()
                        .configure(new SimpleRegistry())
                        .on(liS)
                        .doValidate()
                        .result(toComplex());

默認(rèn)的,F(xiàn)luentValidator使用SimpleRegistry辆飘,它會嘗試從當(dāng)前的class loader中調(diào)用Class.newInstance()方法來新建一個Validator啦辐。

5.2 分組驗證

當(dāng)使用注解驗證時候,會遇到這樣的情況劈猪,某些時候例如添加操作昧甘,我們會驗證A/B/C三個屬性,而修改操作战得,我們需要驗證B/C/D/E 4個屬性
@FluentValidate注解另外一個接受的參數(shù)是groups充边,里面也是Class[]數(shù)組,只不過這個Class可以是開發(fā)人員隨意寫的一個簡單的類常侦,不含有任何屬性方法都可以浇冰,例如:

@Data
@AllArgsConstructor
public class StudentDto {

    @NotNull private String name;

    private Integer age;

    @Length(max = 5)
    @FluentValidate(
            value = {SchoolNameValidator.class},
            groups = {Add.class})
    private String schoolName;

    @FluentValidate(
            value = {AreaValidator.class},
            groups = Update.class)
    private String area;
}

那么驗證的時候,只需要在checkAll()方法中傳入想要驗證的group聋亡,就只會做選擇性的分組驗證肘习,例如下面例子,只有area(地區(qū))會被驗證坡倔。

  ComplexResult result =
                FluentValidator.checkAll(new Class<?>[] {Update.class})
                        .on(liS)
                        .doValidate()
                        .result(toComplex());

5.3 級聯(lián)驗證

級聯(lián)驗證(cascade validation)漂佩,也叫做對象圖(object graphs),指一個類嵌套另外一個類的時候做的驗證罪塔。
如下例所示投蝉,我們在車庫(Garage)類中含有一個汽車列表(carList),可以在這個汽車列表屬性上使用@FluentValid注解征堪,表示需要級聯(lián)到內(nèi)部Car做onEach驗證瘩缆。

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

注意,@FluentValid和@FluentValidate兩個注解不互相沖突佃蚜,如下所示庸娱,調(diào)用鏈會先驗證carList上的CarNotExceedLimitValidator,然后再遍歷carList谐算,對每個car做內(nèi)部的生產(chǎn)商熟尉、座椅數(shù)、牌照驗證洲脂。

6.SpringBoot實戰(zhàn)

6.1 添加依賴

fluent-validator 集成 spring 需要添加依賴

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

6.2 注冊 Fluent-validator

fluent-validate 與 spring 結(jié)合使用 annotation 方式進(jìn)行參數(shù)校驗臣樱,需要借助于 spring 的 AOP,fluent-validate 提供了處理類 FluentValidateInterceptor,但是 fluent-validate 提供的默認(rèn)驗證回調(diào)類 DefaultValidateCallback 對校驗失敗的情況并沒有處理雇毫,所以需要自行實現(xiàn)一個

@Slf4j
public class MyValidateCallBack extends DefaultValidateCallback implements ValidateCallback {
    @Override
    public void onSuccess(ValidatorElementList validatorElementList) {
        log.info("校驗成功");
        super.onSuccess(validatorElementList);
    }

    @Override
    public void onFail(ValidatorElementList validatorElementList, List<ValidationError> errors) {
        log.info("校驗失敗");
        throw new RuntimeException(errors.get(0).getErrorMsg());
    }

    @Override
    public void onUncaughtException(Validator validator, Exception e, Object target)
            throws Exception {
        log.info("校驗異常");
        throw new RuntimeException(e);
    }

6.3 注冊IOC

注冊 FluentValidateInterceptor攔截器及MyValidateCallBack回調(diào)方法玄捕,最后配置一個 AOP 規(guī)則

@Configuration
public class ValidateCallbackConfig {

    @Bean
    public FluentValidateInterceptor fluentValidateInterceptor() {
        FluentValidateInterceptor fluentValidateInterceptor = new FluentValidateInterceptor();
        fluentValidateInterceptor.setCallback(validateCallback());
        return fluentValidateInterceptor;
    }

    public MyValidateCallBack validateCallback() {
        return new MyValidateCallBack();
    }

    @Bean
    public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
        // 使用BeanNameAutoProxyCreator來創(chuàng)建代理
        BeanNameAutoProxyCreator proxyCreator = new BeanNameAutoProxyCreator();
        // 設(shè)置要創(chuàng)建代理的那些Bean的名字
        proxyCreator.setBeanNames("*ServiceImpl");
        proxyCreator.setInterceptorNames("fluentValidateInterceptor");
        return proxyCreator;
    }
}

6.4 使用校驗

為了方便,在StudentServiceImpl實現(xiàn)類上增加參數(shù)校驗

@Service
public class StudentServiceImpl implements StudentService {
    @Override
    public Integer getAge(@FluentValid StudentDto studentDto) {
        return studentDto.getAge();
    }
}

總結(jié)

fluent-validate 可以全方位兼容 hibernate-validate棚放,基于 spring 的 AOP 可以提供基于注解的方法入?yún)⑿r灻墩常瑫r也可以提供流式編程的工具類業(yè)務(wù)校驗,替代 hibernate-validate 的同時提供了更多擴展性

參考文檔:http://neoremind.com/2016/02/java%E7%9A%84%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E9%AA%8C%E8%AF%81%E6%A1%86%E6%9E%B6fluent-validator/

歡迎大家訪問 個人博客 Johnny小屋

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末飘蚯,一起剝皮案震驚了整個濱河市馍迄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌局骤,老刑警劉巖攀圈,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異峦甩,居然都是意外死亡赘来,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門凯傲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犬辰,“玉大人,你說我怎么就攤上這事冰单』戏欤” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵诫欠,是天一觀的道長涵卵。 經(jīng)常有香客問我,道長荒叼,這世上最難降的妖魔是什么轿偎? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮甩挫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘椿每。我一直安慰自己伊者,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布间护。 她就那樣靜靜地躺著亦渗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪汁尺。 梳的紋絲不亂的頭發(fā)上法精,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機與錄音,去河邊找鬼搂蜓。 笑死狼荞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的帮碰。 我是一名探鬼主播相味,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼殉挽!你這毒婦竟也來了丰涉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤斯碌,失蹤者是張志新(化名)和其女友劉穎一死,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體傻唾,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡投慈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了策吠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛裤。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猴抹,靈堂內(nèi)的尸體忽然破棺而出带族,到底是詐尸還是另有隱情,我是刑警寧澤蟀给,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布蝙砌,位于F島的核電站,受9級特大地震影響跋理,放射性物質(zhì)發(fā)生泄漏择克。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一前普、第九天 我趴在偏房一處隱蔽的房頂上張望肚邢。 院中可真熱鬧,春花似錦拭卿、人聲如沸骡湖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽响蕴。三九已至,卻和暖如春惠桃,著一層夾襖步出監(jiān)牢的瞬間浦夷,已是汗流浹背辖试。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劈狐,地道東北人罐孝。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像懈息,于是被迫代替她去往敵國和親肾档。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容