简介:心愿本文能够帮忙到大家,能够用一种优雅形式接入参数校验,爱护零碎解放本身,从你我做起!
作者 | 中野
起源 | 阿里技术公众号
一 不厌其烦的 if else?
参数校验,为了爱护本人的代码,个别都会在开发中假如所有的参数都是不牢靠的。针对所有的参数校验场景本人一次进行判断及错误信息的提醒。
例如:
if(a.size > 10 && a.size < 100){Result result = Reuslt.fail("非法参数 size , 请查看输出!") ;
return result;
}
if(xxx)
return xxx ;
还有一种 case,重一点的业务参数校验,有时候也会被不厌其烦地校验,散落在各个子系统或者零碎的各处模块代码中。
例如:
if(!validItem(itemId)){Result result = Reuslt.fail("不存在的商品 id , 请查看输出!") ;
return result;
}
private boolean validItem(itemId){
// RPC getItem
// 是否空判断
return item != null;
}
针对以上的场景,本文探讨一下如何优雅地在业务零碎中做参数校验,分享构建通用校验模块的一些实际。
二 业内框架 hibernate validator
1 简介
JSR 提供了一套 Bean 校验标准的 API,保护在包 javax.validation.constraints 下。该标准应用属性或者办法参数或者类上的一套简洁易用的注解来做参数校验。开发者在开发过程中,仅需在须要校验的中央加上形如 @NotNull, @NotEmpty , @Email 的注解,就能够将参数校验的重任委托给一些第三方校验框架来解决。
引自网络:
JSR-303 是 JAVA EE 6 中的一项子标准,叫做 Bean Validation,官网参考实现是 Hibernate Validator。
此实现与 Hibernate ORM 没有任何关系。JSR 303 用于对 Java Bean 中的字段的值进行验证。
Spring MVC 3.x 之中也大力支持 JSR-303,能够在控制器中对表单提交的数据不便地验证。
注:能够应用注解的形式进行验证。
接入 validation api 及 hibernate validator 后,做一般的参数校验简略到不行:
Hibernate Validator 反对了一系列的如非空,无效邮箱,正则表达式是否匹配等一系列根底校验反对:
@Email
@NotNull
@Pattern
@AssertFalse
……
而像业务零碎中常见的校验,Hibernate Validator 是无奈反对的,例如校验订单号是否无效,订单上的商品 id 是否真实有效,这种校验 Hibernate 也留了口子,能够自行定义注解,同时自行定义校验逻辑后依赖 SPI 机制注册到 Hibernate Validator 中即可。
如何自定义业务参数校验 API 及其校验实现,能够参考官网文档,不再赘述 Hibernate Validator 的用法。
2 实现原理
能够想下如果本人做一套反对 JSR303 bean 校验标准的校验框架,咱们会如何实现。
其实无非是读取 class 元数据,获取 bean 类上的所有带有校验注解的属性,在每次须要校验对象的时候,拿到对象对应属性的值来与其上的所有校验注解来执行校验实现逻辑,而后收集所有不通过的信息。
hibernate validator 的实现外围原理也是如此:
上图仅展现一些 Hibernate validator 的外围组件,实际上有十分多的细节,不在此赘述,有趣味理解全流程的同学能够自行 debug 一下,并不是非常复杂。
校验的过程:
- 配置 Hibernate Validator,把所有相干的非懒加载的外围组件都进行初始化,依赖 java 的 SPI 机制反对自定义 validator
- 进行校验,先进行 class 数据解析,而后获取对应属性的对应 validator 进行校验,最初通过 MessageInterpolator 组件进行校验错误信息的提取。依赖 java 的 ResourceBundle 机制反对校验信息多语言。
三 优雅实际
基于 hibernate validator,怎么能够做一些优雅实际呢?
hibernate validator 仅是 bean 校验框架,可能还须要做一些适配才能够让咱们在业务零碎开发中,上面分享一下一些开发实际,外围谋求的是业务逻辑与参数校验逻辑齐全解耦合,罕用的业务参数校验逻辑能够在多套业务零碎中被复用以及对立保护所有的校验错误信息。
概要图:
RPC 与 WEB 零碎部署架构图:
- 拦挡所有申请,能够基于 RPC filter 和 Spring MVC 的 HandlerInterceptor 来实现 RPC 申请,和 HTTP 申请的拦挡,拦截器中应用 validator 校验参数,失败的话间接设置失败信息,疾速返回。
- 对立参数校验包,纯正的校验 API,所有校验以注解模式做形象,反对简略复用 , 形如 @NotNull , @ExistItem , @ExistBarcode 的作用于参数上的注解,这个能够复用 JSR 校验标准来实现。
- 对立参数校验的实现(validator),所有的校验注解对应的校验逻辑实现以对立 maven 依赖模式提供,和校验 API 一一对应。
- hibernate validator 进行扩大,校验错误信息解析对立保护于配置核心,接入在配置核心能够在运行时动静批改,以本地文件模式存储校验提示信息也并无不可,只是保护起来简单麻烦。
- 保护简略易用的 starter,开箱即用,反对所有业务零碎疾速接入。
一些代码实现(须要自取):
RPC filter & ResourceBundle
// 应用自定义的配置核心信息源 初始化 validator
public static Validator validator;
static {
HibernateValidatorConfiguration configure =
Validation.byProvider(HibernateValidator.class).configure();
ResourceBundleLocator defaultResourceBundleLocator =
configure.getDefaultResourceBundleLocator();
ResourceBundleLocator myResourceBundleLocator =
new MyResourceBundleLocator(defaultResourceBundleLocator);
configure.messageInterpolator(new ResourceBundleMessageInterpolator(myResourceBundleLocator));
configure.enableTraversableResolverResultCache(false);
validator = configure.buildValidatorFactory().getValidator();
}
// RPC 服务:校验失败时候间接 mockresponse 疾速返回,response 中设置 errorMsg
String message = collectValidateMessage(args);
if (StringUtils.isNotEmpty(message)) {
// fail fast
RPCResult rpcResult = new RPCResult();
rpcResult.setHsfResponse(new HSFResponse());
rpcResult.setAppResponse(mockResponse(invocation, message));
SettableFuture<RPCResult> defaultRPCFuture = Futures.createSettableFuture();
defaultRPCFuture.set(rpcResult);
return defaultRPCFuture;
// 配置核心的 ResourceBundle
public class DiamondResourceBundle extends ResourceBundle {private static final Properties properties = new Properties();
public DiamondResourceBundle() {
try {init();
} catch (IOException e) {log.error("初始化 diamond 数据失败", e);
}
}
private void init() throws IOException {
// load once
loadConfig(Diamond.getConfig(DATA_ID, GROUP_ID, 5000));
// add listener
Diamond.addListener(DATA_ID, GROUP_ID, new ManagerListener() {
@Override
public Executor getExecutor() {return pushExecutor;}
@Override
public void receiveConfigInfo(String configInfo) {log.error("receive config : {}", configInfo);
// load config
loadConfig(configInfo);
clearCache();}
});
}
}
四 优良框架校验实现
实际上参数校验是所有 coder 都会遇到的问题,如何更加优雅地解决参数校验的问题呢?
列举一些框架,一起学习一下他们如何做参数校验:
1 Spring
Spring 没有应用任何的参数校验框架,应用其保护的 Assert 工具类 + 罕用的参数异样来做参数校验。所有的参数校验都是在编码时候书写的。
都是应用 Assert.notNull , Assert.notEmpty 等来做参数校验。
Spring 次要还是面向开发的框架,呈现参数异样其信息是面向开发者的,与咱们这种面向用户的校验存在区别。不会呈现业务参数校验失败的状况,人肉校验简略参数也无可非议。
而且 Spring 是作用在利用启动时候的框架,对用户实践上无影响。
2 Feign
看了一些如 Feign 的根底框架,都是手动校验的参数,不简单,这里不一一列举了。
根底框架和业务零碎有基本上的差别,根底框架是面向开发人员的框架,大部分都是在零碎部署时候启动,校验有异样的话都是间接抛出,开发人员能够依据错误信息及时排查。
而业务零碎,敲代码嗖嗖嗖的敲完了业务逻辑,如果参数传的有误,可能会间接导致系统不可用。
这种状况可能因为接口调用方没有应用精确参数,前端没有做参数校验等等,但无论如何,咱们必须保障本身零碎是稳固牢靠的,尤其须要使咱们的零碎远离“内部”的有效数据,注意每一个参数的可靠性及边界状况。
五 总结
最初分享一下防御性编程的一些准则,心愿你我一起严格依照准则来爱护线上零碎。
引自网络:
Steve McConnell 的经典编程之书——《Code Complete》,用一个短篇解释了防御性编程的一些根本规定:
- 爱护你的代码远离来自“内部”的有效数据,无论这个“内部”的概念被定位为什么。它能够是来自于内部零碎、用户、文件的数据,也能够是模块 / 组件以外的数据,由你决定。建立“路障”、“安全区”或“信赖边界”——在边界之外的一切都是危险的,界线之内的所有都是平安的。对于“路障”代码,须要验证所有的输出数据:查看所有输出参数的类型、长度和值域是否正确。还要加倍查看限度和界线。
- 当咱们查看出谬误数据后,还须要决定如何解决它。防御性编程不会覆盖谬误,也不会暗藏 bug。这须要在健壮性(如果问题能够解决那就持续运行)和正确性(不返回不精确的后果)之间做衡量。抉择好策略来应答谬误数据:返回谬误就马上进行,返回中性值就替换数据值……确保策略明确且一贯。
- 不要将代码内部的函数调用或办法调用想得太过美妙。请确保你调用内部的 API 和库之前了解并测试了谬误。
- 至多在开发和测试阶段,要应用断言记录假如,并高亮“不可能”的条件。这在大型零碎中显得尤为重要,因为随着工夫的推移,将会有不同的程序员用高度牢靠的代码来保护这些大型零碎。
- 增加诊断代码,智能地记录和跟踪以帮忙解释在运行时产生的事件,尤其是当你遇到问题的时候。
- 标准化的错误处理。想好如何解决“失常谬误”、“预期谬误”以及正告,并对此司空见惯。
- 只有当你真的须要的时候,才应用异样解决,并确保你得彻底了解该编程语言的异样处理程序。
搞起来,心愿本文能够帮忙到大家,能够用一种优雅形式接入参数校验,爱护零碎解放本身,从你我做起!
原文链接
本文为阿里云原创内容,未经容许不得转载。