Java的业务OA幸运飞艇平台出租逻辑验证架fluent-validator

在互联网OA幸运飞艇平台出租haozbbs.comQ1446595067 行业中,基于Java开发的业务类系统,不管是服务端还是客户端,业务逻辑代码的更新往往是非常频繁的,这源于功能的快速迭代特性。在一般公司内部,特别是使用Java web技术构建的平台中,不管是基于模块化还是服务化的,业务逻辑都会相对复杂。

这些系统之间、系统内部往往存在大量的API接口,这些接口一般都需要对入参(输入参数的简称)做校验,以保证:
1) 核心业务逻辑能够顺利按照预期执行。
2) 数据能够正常存取。
3) 数据安全性。包括符合约束以及限制,有访问权限控制以及不出现SQL注入等问题。

开发人员在维护核心业务逻辑的同时,还需要为输入做严格的校验。当输入不合法时,能够给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
}

我们不能说这是反模式(anti-pattern),但是从中我们可以发现,它不够优雅而且违反一些范式:
1)违反单一职责原则(Single responsibility)。核心业务逻辑(core business logic)和验证逻辑(validation logic)耦合在一个类中。
2)开闭原则(Open/closed)。我们应该对扩展开放,对修改封闭,验证逻辑不好扩展,而且一旦需要修改需要动整体这个类。
3)DRY原则(Don’t repeat yourself)。代码冗余,相同逻辑可能散落多处,长此以往不好收殓。

2 为何要使用FluentValidator

原因很简单,第一为了优雅,出色的程序员都有点洁癖,都希望让验证看起来很舒服;第二,为了尽最大可能符合这些优秀的原则,做clean code。

FluentValidator就是这么一个工具类库,适用于以Java语言开发的程序,让开发人员回归focus到业务逻辑上,使用流式(Fluent Interface)调用风格让验证跑起来很优雅,同时验证器(Validator)可以做到开闭原则,实现最大程度的复用。

3 FluentValidator特点

这里算是Quick learn了解下,也当且看做打广告吧,看了这些特点,希望能给你往下继续阅读的兴趣:)

1) 验证逻辑与业务逻辑不再耦合
摒弃原来不规范的验证逻辑散落的现象。

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...

原文地址:http://blog.51cto.com/13864280/2140289

时间: 2024-10-09 01:21:09

Java的业务OA幸运飞艇平台出租逻辑验证架fluent-validator的相关文章

利用padding-top/padding-bottom百分比OA幸运飞艇平台,进行占位和高度自适应

在css里面,padding-top,padding-bottom,margin-top,margin-bottom取值为百分比的时候,参照的是父元素的宽度. 比如:父元素宽度是100px, 子元素padding-top:50%,那么padding-top的实际值就是100*50%=50px 这个小小的知识点,其实有很大的用处,应用也很广泛,就是进行提前占位,避免资源加载时候的闪烁,还可以让高度自适应. 举例: 一般来说,想要自适应屏幕大小,我们设置元素的宽度自适应是完全没有问题的,比如希望一行

机器学习实践心得:数据平台设计与搭建US幸运飞艇平台出租

机器学习作为近几年的一项热门技术US幸运飞艇平台出租QQ2952777280[话仙源码论坛]hxforum.com[木瓜源码论坛]papayabbs.com,不仅凭借众多"人工智能"产品而为人所熟知,更是从根本上增能了传统的互联网产品.下文将基于本人所负责的个推大数据平台搭建工作,与大家分享个推数据平台架构方面的经验以及踩过的一些坑. 一.背景:机器学习在个推业务中的应用场景作为独立的智能大数据服务商,个推主要业务包括开发者服务.精准营销服务和各垂直领域的大数据服务.而机器学习技术在多

Python爬虫爬取OA幸运飞艇平台获取数据

安装BeautifulSoup以及requests 打开window 的cmd窗口输入命令pip install requests 执行安装,等待他安装完成就可以了 BeautifulSoup库也是同样的方法 我使用的编译器的是sublime text 3,觉得是挺好用的一个编译软件 其他工具: Chrome浏览器 Python版本: Python3.6 运行平台: Windows 1.首先我们搜索OA幸运飞艇平台排行榜:[×××.com/h5]企 娥:217 1793 408获取网页的代码:

功能、界面/易用性幸运飞艇平台出租、中断、网络、兼容性、安全性、性能测试

功能测试幸运飞艇平台出租(www.1159880099.com)QQ11598800991.朋友圈发送功能 1)只发送文本 a.考虑文本长度:1-1500字符(该数据为百度数据).超出最大字符长度 b.考虑文本类型:纯中文.纯数字.纯字母.纯字符.纯表情(微信表情/手机自带表情).混合类型.包含url链接:因为过长纯类型需要换行很容易出现超出边框问题,所以这里先考虑过长纯类型情况 c.文本是否支持复制粘贴 d.为空验证 2)只发送图片 a.本地相册选择/拍摄 b.图片数量验证:1-9张图片.超出

MEF 插件幸运飞艇平台出租式开发 - DotNetCore 中强大的 DI

背景叙述在前面几篇幸运飞艇平台出租(www.1159880099.com)QQ1159880099 MEF 插件式开发 系列博客中,我分别在 DotNet Framework 和 DotNet Core 两种框架下实验了 MEF 的简单实验,由于 DotNet Framework 由来已久,因此基于该框架下衍生出的很多优秀的 MEF 框架较多.但是对于 DotNet Core 来说,情况有所不同,由于它本身对 DI 内置并提供支持,因此我尝试使用它的全新 依赖注入(DI) 来做一些实验. 动手实

Java创建极速飞艇平台出租线程的三种方式

(1)定义Thread类的子类,极速飞艇平台出租[企鹅21717-93408]并重写该类的run方法,该run方法的方法体就代 表了线程要完成的任务.因此把run()方法称为执行体.(2)创建Thread子类的实例,即创建了线程对象.(3)调用线程对象的start()方法来启动该线程. 二.通过Runnable接口创建线程类(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体是该线程的线程执行体.(2)创建 Runnable实现类的实例,并依此实例作为T

.Net4.6 Task 异步OA现金盘平台出租函数 比 同步函数 慢5倍 踩坑经历

异步Task简单介绍本标题有点 哗众取宠OA现金盘平台出租QQ2952777280[话仙源码论坛]hxforum.com[木瓜源码论坛]papayabbs.com ,各位都别介意(不排除个人技术能力问题) -- 接下来:我将会用一个小Demo 把 本文思想阐述清楚. .Net 4.0 就有了 Task 函数 -- 异步编程模型 .Net 4.6 给 Task 增加了好几个 特别实用的方法,而且引入了 await async 语法糖 当然,这是非常不错的技术,奈何我有自己的线程队列封装,也就没有着

Linux中用Nginx和FTP搭建WS幸运飞艇平台搭建图片服务器

一.需要的组件WS幸运飞艇平台搭建论坛:haozbbs.com Q1446595067 图片服务器两个服务:Nginx(图片访问): 1.http服务:可以使用nginx做静态资源服务器.也可以使用apache.推荐使用nginx,效率更高. 2.反向代理 实现 负载均衡ftp服务(图片上传): 使用linux做服务器,在linux中有个ftp组件vsftpd.二.Nginx服务器搭建1.安装Nginx 要求安装vmware虚拟机. Linux:CentOS6.4(32) Nginx:1.8.0

Java8函数OA现金盘平台出租式编程实践精华

现在是OA现金盘平台出租haozbbs.comQ1446595067 资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享. 绪论 从java8开始,我们就可以通过java8中的StrameAPI与Lambda表达式实现函数式编程,可以让代码变得更加高效简洁. 现在很多企业的生产代码已经开始使用java8了,对于还没有使用过java8进行的编程的朋友们可以好好的学习一下,我在企业中写java8也有一段时间了,我想把我在实际开发中用到的一些场景与大家分享一下,大部分