手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)
前言
之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一、校验工具类不一致、维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验。
遇到的问题
在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{code:999,msg:"参数校验失败",data:null} 这种,但是原生的JSR303并不支持自定义的字段,所以需要自定义校验注解。针对这个问题我参考一些JSR303的资料,对其进行了一个定制扩展,以达到开发人员不需要关注捕捉和封装返回信息。
传统的校验做法
如下校验如果一个实体里面上百个字段需要校验的话,对于维护起来是一个很麻烦的事情,而且很多校验可以通过jsr-303的注解方式统一处理,无需写一大堆if和else
if(name == null) {
//返回错误信息
}else if(age == null) {
//返回错误信息
}
基于jsr-303定制后的校验
- 定义一个自定义非空注解
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { CorpNotEmptyValidator.class })
public @interface CorpNotEmpty {
//自定义字段
String field() default "";
//返回错误码
String code() default "0";
//错误消息
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
CorpNotEmpty[] value();
}
}
2.定义非空注解对应的校验器, initialize和isValid作用描述如下:
- initialize方法主要是初始化ReturnCodeModel,用于当校验参数不通过后返回,ReturnCodeModel里面主要是封装了返回体,如 code,message等
- isValid主要是自定义校验器的校验规则,如下判断是否为空使用 StringUtils.isEmpty方法,如果校验不通过则set flag为false,然后调用基类的isValid方法,该基类方法会判断flag是否为false,如果是false说明不通过
public class CorpNotEmptyValidator extends BaseCorpValidator<CorpNotEmpty,String> {
@Override
public void initialize(CorpNotEmpty constraintAnnotation) {
model = new ReturnCodeModel();
model.setCode(constraintAnnotation.code());
model.setErrorMsg(constraintAnnotation.message());
model.setField(constraintAnnotation.field());
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
System.out.println("1");
if(StringUtils.isEmpty(s)){
model.setFlag(false);
}else{
model.setFlag(true);
}
return super.isValid(s,constraintValidatorContext);
}
}
3.定义一个基类,实现 ConstraintValidator,主要是因为需要把isValid这个方法定义成抽象方法提供给不同的校验器使用,避免其它校验器写重复的代码
public abstract class BaseCorpValidator<A extends Annotation,B> implements ConstraintValidator<A ,B> {
protected ReturnCodeModel model = null;
@Override
public boolean isValid(B value, ConstraintValidatorContext context) {
if(!model.getFlag()){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(JSON.toJSONString(model)).addConstraintViolation();
return false;
}
return true;
}
}
4.测试类
public class TestV1 {
static ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
static Validator validator = validatorFactory.getValidator();
public static void main(String[] args) {
UserModel userModel = new UserModel();
userModel.setName("aa");
userModel.setDate("2011");
Set<ConstraintViolation<UserModel>> constraintViolations = validator.validate(userModel);
//判断constraintViolations是否为空,不为空说明校验不通过,拿到ReturnCodeModel里面的错误信息后返回给客户端
if(!constraintViolations.isEmpty()){
for (ConstraintViolation<?> item : constraintViolations) {
ReturnCodeModel codeModel = JSON.parseObject(item.getMessage(), ReturnCodeModel.class);
System.out.println(JSON.toJSONString(codeModel));
}
}
}
}
画外音:场景考虑
1.比如name这个字段,要满足既不能为空又只能为数字这2个情况,如果把2个校验方法都写在同一个校验器,则其他开发使用的时候也会影响到,所以需要有2个注解的方式,一个是校验为空,一个是校验是否位数字,分析完后那么就存在一个先后顺序问题(因为自己在本地测试出现有可能会先执行校验是否位数字的校验器,这时候就会出现空指针异常), 所以针对这个场景需要自定义一个顺序注解。
如下代码,在需要校验的model实体上加入@GroupSequence注解,这样校验器底层会帮我们按照顺序依次处理
//顺序注解
@GroupSequence({
First.class,
Two.class,
Three.class,
UserModel.class
})
public class UserModel {
@CorpMustNumber(code="-2",message="必须数字",groups=Two.class)//在执行数字校验
@CorpNotEmpty(code="-1",message="姓名必填",groups=First.class)//先执行非空
private String name;
@CorpNotEmpty(code="-1",message="日期必填")
private String date;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
} }
总结
以上就是本篇博客涉及到技术点的所有代码,通过定制自己的校验器以满足公司业务场景,对于开发来说统一了规范,统一风格,对以后维护还是扩展都非常方便。 如果博文对你有帮助麻烦点个关注或者赞,谢谢!
手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)的更多相关文章
- 用Kotlin写一个基于Spring Boot的RESTful服务
Spring太复杂了,配置这个东西简直就是浪费生命.尤其在没有什么并发压力,随便搞一个RESTful服务 让整个业务跑起来先的情况下,更是么有必要纠结在一堆的XML配置上.显然这么想的人是很多的,于是 ...
- Spring Boot 框架下使用MyBatis访问数据库之基于XML配置的方式
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML ...
- 在Spring Boot框架下使用WebSocket实现消息推送
Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的 ...
- 在Spring Boot框架下使用WebSocket实现聊天功能
上一篇博客我们介绍了在Spring Boot框架下使用WebSocket实现消息推送,消息推送是一对多,服务器发消息发送给所有的浏览器,这次我们来看看如何使用WebSocket实现消息的一对一发送,模 ...
- Spring Boot 之:接口参数校验
Spring Boot 之:接口参数校验,学习资料 网址 SpringBoot(八) JSR-303 数据验证(写的比较好) https://qq343509740.gitee.io/2018/07/ ...
- 基于Spring Boot框架开发的一个Mock
背景:在项目后端接口开发还未完成,我们无法进行自动化接口用例的调试,希望与开发同步完成接口自动化用例的编写及调试,待项目转测后,可以直接跑自动化用例,提高测试效率. 选用的maven + Spring ...
- SpringBootService,一个基于spring boot搭建的SOA服务框架
SpringBootService,这是一个spring boot微服务的框架,包括redis,mq,restful,定时器,mybatis.易扩容.易维护的架构. 项目说明 该项目使用maven进行 ...
- Spring Boot入门第二天:一个基于Spring Boot的Web应用,使用了Spring Data JPA和Freemarker。
原文链接 今天打算从数据库中取数据,并展示到视图中.不多说,先上图: 第一步:添加依赖.打开pom.xml文件,添加必要的依赖,完整代码如下: <?xml version="1.0&q ...
- 如何基于Spring Boot搭建一个完整的项目
前言 使用Spring Boot做后台项目开发也快半年了,由于之前有过基于Spring开发的项目经验,相比之下觉得Spring Boot就是天堂,开箱即用来形容是绝不为过的.在没有接触Spring B ...
随机推荐
- 台州学院第十二届校赛记录(B,C,E,H,I,J,L)
传送门:点我 题目很棒,感谢出题验题的大佬们. 细节坑不少,是好事. 还是很菜,继续加油! B: 桃子的生日 时间限制(普通/Java):1000MS/3000MS 内存限制:65536KBy ...
- CachedRowSet 接口
Sun Microsystems 提供的 CachedRowSet 接口的参考实现是一个标准实现.开发人员可以按原样使用此实现.可以扩展它,也可以选择自己编写此接口的实现. CachedRowSet ...
- 不同RAM空间存储变量区分
- (1)51单片机NOP指令
提问:什么是NOP指令?干什么用的?单片机程序里执行一条nop指令需要多长时间? (1)一个NOP就是一个机器周期 (2)空指令,延时一个机器周期 (3)这个与单片机型号.指令类型和使用的晶振频率有关 ...
- Spring Boot 定时+多线程执行
Spring Boot 定时任务有多种实现方式,我在一个微型项目中通过注解方式执行定时任务. 具体执行的任务,通过多线程方式执行,单线程执行需要1小时的任务,多线程下5分钟就完成了. 执行效率提升10 ...
- mysql主从之keepalive+MySQL高可用
一 keepalive介绍 1.1 keepalived 是什么 keepalived 是集群管理中保证集群高可用的一个服务软件,用来防止单点故障. 1.2 keepalived 工作原理 keepa ...
- HDU2089 不要62 题解 数位DP
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 题目大意:求区间 \([l,r]\) 范围内不包含数字"4"且不包含连续的数 ...
- 《算法笔记》之基础C/C++入门
开始进行算法笔记的学习,在此纪录下来,同时作为自己日后复习资料. 1.基本语法 #include <iostream> using namespace std; int main(){ c ...
- 830. String Sort
830. String Sort 题解 int alpha[256] = {0};//记录字符的次数 bool cmp(char a,char b) { if(alpha[a]==alpha[b])/ ...
- python文件及目录操作
python文件及目录操作 读/写文件 新建/打开文件 写入 #举个例子,打开D:\test\data.txt #以写入模式打开文件 #如果test(上级目录)不存在则报错 #如果data.txt(文 ...