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中,不然找不到哦。