应用工程中,数据校验是不可或缺的一环,为了避免随处散落和耦合紧密的代码,如何优雅灵活的编写校验代码,提供了一些思路供大家参考。
数据校验大体可以分为两种,参数校验(参数非空,长度等) 和 业务校验(符合业务流转要求)。
参数校验
Preconditions
入参校验是最基础的,一般包括以下几种
- 非空校验
- 业务条件校验
- 下游代码的前置条件校验
- 数据库条件校验
- 第三方HSF接口参数校验
使用 Preconditions 可以简洁优雅的实现,避免过多的if语句出现。
Preconditions.checkNotNull(Object);
复制代码
但是涉及校验项过多,还是会手动编写很多代码,为了避免这种情况,可以对入参使用注解的方式校验
注解式校验 Bean Validation
通过在入参中定义限制条件,可以减少校验代码的编写。
Maven 依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
复制代码
直接在入参对象中定义限制条件。
public class Car {
@NotNull(message = "tire不能为空")
private String tire;
public String getTire() {
return tire;
}
public void setTire(String tire) {
this.tire = tire;
}
}
复制代码
使用 Validator 进行统一校验。
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
@Test
public void testValidate(){
Car car = new Car();
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
assertEquals(2, constraintViolations.size());
}
复制代码
参考链接:
Java Code Examples for javax.validation.Validator
校验器
总的思想: 把会变化的部分取出并封装起来,以便以后可以轻易的改动和扩充此部分,而不影响不需要变化的其他部分
在设计一个业务流程代码的时候,会有很多校验规则,有些同学会将规则校验混淆在业务代码中,各种if-else语句,或是抽象的不够干净,在走读代码的时候真心累,后续代码维护的时候,要增加业务校验,破窗思维惯性会继续在业务代码中增加校验代码,使得代码不断的腐化。
最好的办法是将校验逻辑抽取出来一个专门的校验类, 后续需要修改校验逻辑,不用修改原有的业务流程,集中管理,增加代码的内聚性,并且代码可重用,
对于校验类的返回值,一般有三种
- 简单的规则校验,返回布尔值,简单易懂;
- 针对多种条件返回,可以定义一个枚举值,定义状态码和状态消息,缺点是接收方需要一个枚举解析类;
- 针对异常场景返回一个异常类,将异常信息直接封装在消息中。
public class WithdrawValidator {
static boolean validateAmount(Object param){
// TODO 规则判断
return true;
}
static void validateRights(){
//TODO 规则判断
if( 1 == 1){
throw new ValidationException("权限不足");
}
}
}
复制代码
可组装校验器
工厂模式 & 门面模式
一个复杂系统中,校验规则是无处不在,业务的的易变性极强,每一个场景的数据相关联的业务规则都不相同,对扩展性和可配置性的要求较高,对每一个数据类都要设置专属的校验类。
定义一个接口类和若干规则类,相应的代码如下:
public interface BaseFilter {
void validate(Request request);
}
public class AbstractBaseFilter implements BaseFilter{
@Override
public void validate(Request request) {
if(request == null){
throw new FilterException("cannot be null");
}
}
}
public class DefaultFilter extends AbstractBaseFilter {
DefaultFilter(){}
public void validate(Request request) {
super.validate(request);
if(request.getId() == null){
throw new FilterException("id 不能为null");
}
//TODO
其他业务规则
}
}
public class AdvanceFilter extends AbstractBaseFilter {
AdvanceFilter(){}
public void validate(Request request) {
super.validate(request);
if(request.getId() == null){
throw new FilterException("id 不能为null");
}
//TODO
其他业务规则
}
}
复制代码
一个系统中有很多校验类,我们需要一个管理机制,针对不同的场景取出需要的检验类使用,简单的说,提供一个工厂类,提供实例化、缓存、和查找服务的功能,通过一个工厂类缓存示例,然后创建一个门面类的,取出相应的校验实例;
本例中的实例都是在工厂中实例化,这里的资源也可以是远程的服务,通过HSF获取校验器并注册。
public class FilterFactory {
private static Map<String,BaseFilter> maps = Maps.newHashMap();
public static void register(String name,BaseFilter filter){
maps.put(name,filter);
}
public static BaseFilter getFilter(String name){
return maps.get(name);
}
}
复制代码
最后创建一个门面类,隐藏工厂类,在业务代码中需要校验的时候,只需要引用门面类,获取需要的规则类即可。
public class FilterFacade {
public static void validate(String filterName, Request request){
BaseFilter filter = FilterFactory.getFilter(filterName);
filter.validate(request);
}
}
复制代码
动态策略的链式校验
以上的实现还是比较简单,一个场景和规则类绑定的比较紧密,可复用性也比较弱,如果将规则类再细分,自由组合,能够让业务代码更灵活的使用检验类。
参考SpringSecurity的过滤链模式,可以实现这个目标,每一个链条,其实就是一个包含若干校验器的集合,将需要的检验类封装在一起。
相同的入参,针对不同的场景,提供不同的校验链,链条中的校验器可自由组装;
校验器的代码参考方式二,校验链的代码实现如下:
定义校验链,为每一种场景配置一条校验链。
public class FilterChain {
private String businessName;
private List<BaseFilter> list = Lists.newArrayList();
FilterChain(){}
FilterChain(String bussinessName)
{
this.businessName = bussinessName;
}
// 组装 Filter
FilterChain(String bussinessName,List<BaseFilter> list)
{
this.businessName = bussinessName;
this.list = list;
}
// 组装 Filter
public FilterChain append(BaseFilter validator){
list.add(validator);
return this;
}
public List<BaseFilter> getFilter(){
return list;
}
}
复制代码
定义工厂类管理链条
public class FilterFactory {
private static Map<String, FilterChain> maps = Maps.newHashMap();
public static void register(String name,FilterChain filter){
maps.put(name,filter);
}
public static FilterChain getFilter(String name){
return maps.get(name);
}
/**
* 应用启动的时候初始化模板
*/
public static void init(){
FilterChain filterChainA = new FilterChain();
filterChainA.append(new DefaultFilter());
filterChainA.append(new AdvanceFilter());
FilterChain filterChainB = new FilterChain();
filterChainB.append(new DefaultFilter());
FilterFactory.register("sceneA",filterChainA);
FilterFactory.register("sceneB",filterChainB);
}
}
复制代码
门面类提供调用方法
public class FilterChainFacade {
public void doFilter(String type , Request request){
FilterChain filter = FilterFactory.getFilter(type);
filter.getFilter().iterator().next().validate(request);
}
public static void main(String[] args) {
FilterChainFacade filterChainFacade = new FilterChainFacade();
String type = "withdraw_usd";
Request request = new Request();
filterChainFacade.doFilter(type,request);
}
}
复制代码