java客户端证书校验(的业务逻辑验证框架)
java客户端证书校验(的业务逻辑验证框架)<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto
开发人员在维护核心业务逻辑的同时,还需要为输入做严格的校验。当输入不合法时,能够给 caller 一个明确的反馈,最常见的反馈就是返回封装了 resul t的对象或者抛出 exception。
一些常见的验证代码片段如下所示:
<pre class="prettyprint hljs php" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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
}</pre>
我们不能说这是反模式(anti-pattern),但是从中我们可以发现,它不够优雅而且违反一些范式:
- 违反单一职责原则(Single responsibility)。核心业务逻辑(core business logic)和验证逻辑(validation logic)耦合在一个类中。
-开闭原则(Open/closed)。我们应该对扩展开放,对修改封闭,验证逻辑不好扩展,而且一旦需要修改需要动整体这个类。 - DRY原则(Don’t repeat yourself)。代码冗余,相同逻辑可能散落多处,长此以往不好收殓。
原因很简单,第一为了优雅,出色的程序员都有点洁癖,都希望让验证看起来很舒服;第二,为了尽最大可能符合这些优秀的原则,做 clean code。FluentValidator 就是这么一个工具类库,适用于以Java语言开发的程序,让开发人员回归 focus 到业务逻辑上,使用流式(Fluent Interface)调用风格让验证跑起来很优雅,同时验证器(Validator)可以做到开闭原则,实现最大程度的复用。
FluentValidator 特点这里算是Quick learn了解下,也当且看做打广告吧,看了这些特点,希望能给你往下继续阅读的兴趣:)
- 验证逻辑与业务逻辑不再耦合 摒弃原来不规范的验证逻辑散落的现象。
- 验器各司其职,好维护,可复用,可扩展。 一个校验器(Validator)只负责某个属性或者对象的校验,可以做到职责单一,易于维护,并且可复用。
- 流式风格(Fluent Interface)调用 借助 [Martin 大神提倡的流式 API 风格使用“惰性求值(Lazy evaluation)”式的链式调用,类似 guava、Java8 stream API 的使用体验。
- 使用注解方式验证 可以装饰在属性上,减少硬编码量。
- 支持 JSR 303 – Bean Validation标准 或许你已经使用了 Hibernate Validator,不用抛弃它,FluentValidator 可以站在巨人的肩膀上。
- Spring 良好集成 校验器可以由 Spring IoC 容器托管。校验入参可以直接使用注解,配置好拦截器,核心业务逻辑完全没有验证逻辑的影子,干净利落。
- 回调给予你充分的自由度 验证过程中发生的错误、异常,验证结果的返回,开发人员都可以定制。
项目托管在github上,说明文档全英完成,i18n 化,同时使用 Apache2 License开源。
最新发布的 Jar 包可以在 maven 中央仓库找到,
maven 引入依赖添加如下依赖到 maven 的 pom.xml 文件中:
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>fluent-validator</artifactId>
<version>1.0.5</version>
</dependency></pre>
注:最新 release 请及时参考 github。
上面这个 FluentValidator 是个基础核心包,只依赖于 slf4j 和 log4j,如果你使用 logback,想去掉 log4j,排除掉的方法如下所示:
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>fluent-validator</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency></pre>
开发业务领域模型
从广义角度来说 DTO(Data Transfer Object)、VO(Value Object)、BO(Business Object)、POJO 等都可以看做是业务表达模型。
我们这里创建一个汽车类(Car)的POJO,里面定义了牌照(license plate)、座椅数(seat count)、生产商(manufacturer)。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Car {
private String manufacturer;
private String licensePlate;
private int seatCount;
// getter and setter...
}</pre>
开发一个专职的 Validator
实际这里需要开发三个 Validator,分别对 Car 的3个属性进行校验,这里以座椅数为例展示如何开发一个 Validator,其他两个省略。
<pre class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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;
}
}</pre>
很简单,实现 Validator 接口,泛型T规范这个校验器待验证的对象的类型,继承 ValidatorHandler 可以避免实现一些默认的方法,例如 accept() ,后面会提到, validate() 方法第一个参数是整个校验过程的上下文,第二个参数是待验证对象,也就是座椅数。
验证逻辑很简单,座椅数必须大于1,否则通过 context 放入错误消息并且返回 false,成功返回 true。
开始验证吧二话不说,直接上代码:
<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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);</pre>
我想不用多说,如果你会英文,你就能知道这段代码是如何工作的。这就是流式风格(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 类,如果座椅数不合法,那么控制台打印结果如下:
<pre class="prettyprint hljs vbscript" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Result{ isSuccess=false errors=[Seat count is not valid invalid value=99]}</pre>
深入实践Validator详解
Validator 接口定义如下:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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);
}</pre>
ValidatorHandler 是实现 Validator 接口的一个模板类,如果你自己实现的 Validator 不想覆盖上面3个方法,可以继承这个 ValidatorHandler。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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) {
}
}</pre>
内部校验逻辑发生错误时候,有两个处理办法,
第一,简单处理,直接放入错误消息。
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">context.addErrorMsg("Something is wrong about the car seat count!");
return false;</pre>
第二,需要详细的信息,包括错误消息,错误属性/字段,错误值,错误码,都可以自己定义,放入错误的方法如下, create() 方法传入消息(必填), setErrorCode() 方法设置错误码(选填), setField() 设置错误字段(选填), setInvalidValue() 设置错误值(选填)。当然这些信息需要 result(toComplex()) 才可以获取到,详见6.7小节。
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">context.addError(ValidationError.create("Something is wrong about the car seat count!").setErrorCode(100).setField("seatCount").setInvalidValue(t));
return false;</pre>
ValidatorChain
on() 的一连串调用实际就是构建调用链,因此理所当然可以传入一个调用链。
<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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());</pre>
如果要验证的是一个集合(Collection)或者数组,那么可以使用 onEach,FluentValidator 会自动为你遍历,依次 apply constraints。
<pre class="prettyprint hljs css" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">FluentValidator.checkAll()
.onEach(Lists.newArrayList(new Car() new Car()) new CarValidator());
FluentValidator.checkAll()
.onEach(new Car[]{ } new CarValidator());</pre>
fail fast or fail over
当出现校验失败时,也就是 Validator 的 validate() 方法返回了 false,那么是继续还是直接退出呢?默认为使用 failFast() 方法,直接退出,如果你想继续完成所有校验,使用 failOver() 来 skip 掉。
<pre class="prettyprint hljs css" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">FluentValidator.checkAll().failFast().on(car.getManufacturer() new CarManufacturerValidator());
FluentValidator.checkAll().failOver().on(car.getManufacturer() new CarManufacturerValidator());</pre>
on() 后面可以紧跟一个 when() ,当 when 满足 expression 表达式 on 才启用验证,否则 skip 调用。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">FluentValidator.checkAll().on(car.getManufacturer() new CarManufacturerValidator()).when(a == b)</pre>
验证回调 callback
doValidate() 方法接受一个 ValidateCallback 接口,接口定义如下:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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;
}</pre>
默认的,使用不含参数的 doValidate() 方法,FluentValidator 使用 DefaultValidateCallback,其实现如下,可以看出出错了,成功了什么也不做,有不可控异常的时候直接抛出。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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;
}
}</pre>
如果你不满意这种方式,例如成功的时候打印一些消息,一种实现方式如下:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Result ret = FluentValidator.checkAll()
.on(car.getSeatCount() new CarSeatCountValidator())
.doValidate(new DefaulValidateCallback() {
@Override
public void onSuccess(ValidatorElementList validatorElementList) {
LOG.info("all ok!");
}
}).result(toSimple());</pre>
获取结果
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 {
/**
- 转换为对外结果
- @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 implements Validator {
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
public boolean validate(ValidatorContext context String t) {
Boolean ignoreManufacturer = context.getAttribute("ignoreManufacturer" Boolean.class);
if (ignoreManufacturer != null && ignoreManufacturer) {
return true;
}
// ...
}</pre>
6.10 闭包
通过putClosure2Context()方法,可以往FluentValidator注入一个闭包,这个闭包的作用是在Validator内部可以调用,并且缓存结果到Closure中,这样caller在上层可以获取这个结果。
典型的应用场景是,当需要频繁调用一个RPC的时候,往往该执行线程内部一次调用就够了,多次调用会影响性能,我们就可以缓存住这个结果,在所有Validator间和caller中共享。
下面展示了在caller处存在一个manufacturerService,它假如需要RPC才能获取所有生产商,显然是很耗时的,可以在validator中调用,然后validator内部共享的同时,caller可以利用闭包拿到结果,用于后续的业务逻辑。
Closure<List> closure = new ClosureHandler<List>() {
<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">private List<String> allManufacturers;
@Override
public List<String> getResult() {
return allManufacturers;
}
@Override
public void doExecute(Object... input) {
allManufacturers = manufacturerService.getAllManufacturers();
}</pre>
};
Result ret = FluentValidator.checkAll()
.putClosure2Context(“manufacturerClosure” closure)
.on(car validator)
.doValidate().result(toSimple());
public class CarValidator extends ValidatorHandler implements Validator {
<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@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;
}</pre>
高级玩法与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)。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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...
}</pre>
我们要在这个 Company 类上做校验,首先引入如下的jar包到pom.xml中。
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>fluent-validator-jsr303</artifactId>
<version>1.0.5</version>
</dependency></pre>
默认依赖于 Hibernate Validator 5.2.1.Final 版本。
然后我们使用 HibernateSupportedValidator 这个验证器来在 company 上做校验,它和普通的 Validator 没任何区别。
<pre class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Result ret = FluentValidator.checkAll()
.on(company new HibernateSupportedValidator<Company>().setValidator(validator))
.on(company new CompanyCustomValidator())
.doValidate().result(toSimple());
System.out.println(ret);</pre>
注意这里的HibernateSupportedValidator依赖于javax.validation.Validator的实现,也就是Hibernate Validator,否则它将不会正常工作。
下面是Hibernate Validator官方提供的初始化javax.validation.Validator实现的方法,供参考使用。
<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Locale.setDefault(Locale.ENGLISH); // speicify language
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
javax.validation.Validator validator = factory.getValidator();</pre>
注解验证
一直以来介绍的方式都是通过显示的API调用来进行验证,FluentValidator同样提供简洁的基于注解配置的方式来达到同样的效果。
@FluentValidate 可以装饰在属性上,内部接收一个 Class[] 数组参数,这些个classes必须是Validator的子类,这叫表明在某个属性上依次用这些Validator做验证。举例如下,我们改造下Car这个类:
<pre class="prettyprint hljs groovy" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Car {
@FluentValidate({ CarManufacturerValidator.class})
private String manufacturer;
@FluentValidate({ CarLicensePlateValidator.class})
private String licensePlate;
@FluentValidate({ CarSeatCountValidator.class})
private int seatCount;
// getter and setter ...
}</pre>
然后还是利用on()或者onEach()方法来校验,这里只不过不用传入Validator或者ValidatorChain了。
<pre class="prettyprint hljs nginx" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Car car = getCar();
Result ret = FluentValidator.checkAll().configure(new SimpleRegistry())
.on(car)
.doValidate()
.result(toSimple());</pre>
那么问题来了?不要问我挖掘机(Validator)哪家强,先来说挖掘(Validator)从哪来。
默认的,FluentValidator使用SimpleRegistry,它会尝试从当前的class loader中调用 Class.newInstance() 方法来新建一个Validator,具体使用示例如下,默认的你不需要使用 configure(new SimpleRegistry() )。
一般情况下,SimpleRegistry都够用了。除非你的验证器需要Spring IoC容器管理的bean注入,那么你干脆可以把Validator也用Spring托管,使用@Service或者@Component注解在Validator类上就可以做到,如下所示:
<pre class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Component
public class CarSeatCountValidator extends ValidatorHandler<Integer> implements Validator<Integer> {
// ...
}</pre>
这时候,需要使用SpringApplicationContextRegistry,它的作用就是去Spring的容器中寻找Validator。想要使用Spring增强功能,将下面的maven依赖加入到pom.xml中。
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><dependency>
<groupId>com.baidu.unbiz</groupId>
<artifactId>fluent-validator-spring</artifactId>
<version>1.0.5</version>
</dependency></pre>
依赖树如下,请自觉排除或者更换不想要的版本。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">[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</pre>
让Spring容器去管理SpringApplicationContextRegistry,由于这个类需要实现ApplicationContextAware接口,所以Spring会帮助我们把context注入到里面,我们也就可以随意找bean了。
调用的例子省略,原来怎么校验现在还怎么来。
当使用注解验证时候,会遇到这样的情况,某些时候例如添加操作,我们会验证A/B/C三个属性,而修改操作,我们需要验证B/C/D/E 4个属性,显示调用FluentValidator API的情况下,我们可以做到“想验证什么,就验证什么”。
@FluentValidate 注解另外一个接受的参数是groups,里面也是 Class[] 数组,只不过这个Class可以是开发人员随意写的一个简单的类,不含有任何属性方法都可以,例如:
<pre class="prettyprint hljs groovy" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Add {
}
public class Car {
@FluentValidate(value = { CarManufacturerValidator.class} groups = { Add.class})
private String manufacturer;
}</pre>
那么验证的时候,只需要在 checkAll() 方法中传入想要验证的 group,就只会做选择性的分组验证,例如下面例子,只有生产商会被验证。
<pre class="prettyprint hljs vbscript" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Result ret = FluentValidator.checkAll(new Class<?>[] { Add.class})
.on(car)
.doValidate()
.result(toSimple());</pre>
这种groups的方法同样适用于hibernate validator,想了解更多请参考官方文档。
级联对象图级联验证(cascade validation),也叫做对象图(object graphs),指一个类嵌套另外一个类的时候做的验证。
如下例所示,我们在车库(Garage)类中含有一个汽车列表(carList),可以在这个汽车列表属性上使用@FluentValid注解,表示需要级联到内部Car做onEach验证。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Garage {
@FluentValidate({ CarNotExceedLimitValidator.class})
@FluentValid
private List<Car> carList;
}</pre>
注意, @FluentValid 和 @FluentValidate 两个注解不互相冲突,如下所示,调用链会先验证 carList 上的 CarNotExceedLimitValidator,然后再遍历 carList,对每个 car 做内部的生产商、座椅数、牌照验证。
Spring AOP集成读到这里,你会发现,只介绍了优雅,说好的业务逻辑和验证逻辑解耦合呢?这需要借助Spring AOP技术实现,如下示例,addCar()方法内部上来就是真正的核心业务逻辑,注意参数上的car前面装饰有@FluentValid注解,这表示需要Spring利用切面拦截方法,对参数利用FluentValidator做校验。
<pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Service
public class CarServiceImpl implements CarService {
@Override
public Car addCar(@FluentValid Car car) {
System.out.println("Come on! " car);
return car;
}
}</pre>
Spring XML配置如下,首先定义了ValidateCallback表示该如何处理成功,失败,抛出不可控异常三种情况,因此,即使你拿不到Result结果,也可以做自己的操作,例如下面例子,如果发生错误,抛出CarException,并且消息是第一条错误信息,如果成功则打印"Everything works fine",如果失败,把实际的异常e放入运行时的CarException抛出。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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);
}
}</pre>
将ValidateCarCallback注入到FluentValidateInterceptor中。然后利用BeanNameAutoProxyCreator在*ServiceImpl上用fluentValidateInterceptor拦截做验证,这有点类似Spring事务处理拦截器的使用方式。更多AOP的用法请参考Spring官方文档。
<pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><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></pre>
测试代码如下:
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Test
public 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();
}</pre>
国际化支持
也就是常说的i18n,使用Spring提供的MessageSource来支持,只需要再Spring的XML配置中加入如下配置:
<pre class="prettyprint hljs javascript" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"><bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>error-message</value>
</list>
</property>
</bean></pre>
同时编辑两个国际化文件,路径放在classpath中即可,通常放到resource下面,文件名分别叫做error-message.properties和error-message_zh_CN.properties表示默认(英文)和中文-简体中文消息。
<pre class="prettyprint hljs vim" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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</pre>
注意,其中 {0} 代表和运行时填充的参数。
在Validator使用时,示例如下,只用MessageSupport.getText(…)来获取国际化信息。
<pre class="prettyprint hljs scala" style="padding: 0.5em; font-family: Menlo Monaco Consolas "Courier New" monospace; color: rgb(68 68 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246 246 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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;
}
}</pre>
这里需要注意下,如果使用 HibernateValidator 打的注解,例如 @NotNull , @Length 等,需要将错误信息放到 ValidationMessages.properties和ValidationMessages_zh_CN.properties 中,否则找不到哦。
上面对 FluentValidator 做了一个全面的介绍,从显示的流式风格(Fluent Interface)API 调用,以及各种丰富多样的链操作方法,再到对 JSR303 – Bean Validation 规范的集成,最后介绍了高级点的注解方式验证、支持级联对象图和 Spring AOP 的集成,我想这完成了对 FluentValidator 任务的诠释,更好的帮助开发人员做业务逻辑验证,希望能给你的项目一点帮助,哪怕不是用这个框架,而只是汲取一些思想,然后产生自己的思考,让我们的 clean code 更加“环保”。