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)」式的鏈式調用,相似guava、Java8 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 Validator是JSR 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<Garage> implements Validator<Garage> { 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() > 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("garage.cars") .setInvalidValue(t.getCarList().size())); return false; } } return true; } } |
這裏須要注意下,若是使用HibernateValidator打的註解,例如@NotNull,@Length等,須要將錯誤信息放到ValidationMessages.properties和ValidationMessages_zh_CN.properties中,不然找不到哦。