java开发SpringBoot参数校验过程示例教程
大家好,我是飘渺。
阅读效果还不错,而且被很多号主都转载过,今天我们继续第二篇,来聊聊在sprinboot中如何集成参数校验validator,以及参数校验的高阶技巧(自定义校验,分组校验)。
此文是依赖于前文的代码基础,已经在项目中加入了全局异常校验器。(代码仓库在文末)
首先我们来看看什么是validator参数校验器,为什么需要参数校验?
为什么需要参数校验
在日常的接口开发中,为了防止非法参数对业务造成影响,经常需要对接口的参数做校验,例如登录的时候需要校验用户名密码是否为空,创建用户的时候需要校验邮件、手机号码格式是否准确。靠代码对接口参数一个个校验的话就太繁琐了,代码可读性极差。
validator框架就是为了解决开发人员在开发的时候少写代码,提升开发效率;validator专门用来进行接口参数校验,例如常见的必填校验,email格式校验,用户名必须位于6到12之间 等等…
validator校验框架遵循了jsr-303验证规范(参数校验规范),
jsr是 java specification requests的缩写。
接下来我们看看在springbboot中如何集成参数校验框架。
springboot中集成参数校验
第一步,引入依赖
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-validation</artifactid> </dependency>
注:从 springboot-2.3开始,校验包被独立成了一个 starter组件,所以需要引入validation和web
而 springboot-2.3之前的版本只需要引入 web 依赖就可以了。
第二步,定义要参数校验的实体类
@data public class validvo { private string id; @length(min = 6,max = 12,message = "appid长度必须位于6到12之间") private string appid; @notblank(message = "名字为必填项") private string name; @email(message = "请填写正确的邮箱地址") private string email; private string sex; @notempty(message = "级别不能为空") private string level; }
在实际开发中对于需要校验的字段都需要设置对应的业务提示,即message属性。
常见的约束注解如下:
注解 | 功能 |
---|---|
@assertfalse | 可以为null,如果不为null的话必须为false |
@asserttrue | 可以为null,如果不为null的话必须为true |
@decimalmax | 设置不能超过最大值 |
@decimalmin | 设置不能超过最小值 |
@digits | 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@future | 日期必须在当前日期的未来 |
@past | 日期必须在当前日期的过去 |
@max | 最大不得超过此最大值 |
@min | 最大不得小于此最小值 |
@notnull | 不能为null,可以是空 |
@null | 必须为null |
@pattern | 必须满足指定的正则表达式 |
@size | 集合、数组、map等的size()值必须在指定范围内 |
必须是email格式 | |
@length | 长度必须在指定范围内 |
@notblank | 字符串不能为null,字符串trim()后也不能等于“” |
@notempty | 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“” |
@range | 值必须在指定范围内 |
@url | 必须是一个url |
注:此表格只是简单的对注解功能的说明,并没有对每一个注解的属性进行说明;可详见源码。
第三步,定义校验类进行测试
@restcontroller @slf4j @validated public class validcontroller { @apioperation("requestbody校验") @postmapping("/valid/test1") public string test1(@validated @requestbody validvo validvo){ log.info("validentity is {}", validvo); return "test1 valid success"; } @apioperation("form校验") @postmapping(value = "/valid/test2") public string test2(@validated validvo validvo){ log.info("validentity is {}", validvo); return "test2 valid success"; } @apioperation("单参数校验") @postmapping(value = "/valid/test3") public string test3(@email string email){ log.info("email is {}", email); return "email valid success"; } }
这里我们先定义三个方法test1,test2,test3,test1使用了 @requestbody
注解,用于接受前端发送的json数据,test2模拟表单提交,test3模拟单参数提交。注意,当使用单参数校验时需要在controller上加上@validated注解,否则不生效。
第四步,体验效果
调用test1方法
提示的是org.springframework.web.bind.methodargumentnotvalidexception异常
post http://localhost:8080/valid/test1 content-type: application/json { "id": 1, "level": "12", "email": "47693899", "appid": "ab1c" }
{ "status": 500, "message": "validation failed for argument [0] in public java.lang.string com.jianzh5.blog.valid.validcontroller.test1(com.jianzh5.blog.valid.validvo) with 3 errors: [field error in object 'validvo' on field 'email': rejected value [47693899]; codes [email.validvo.email,email.email,email.java.lang.string,email]; arguments [org.springframework.context.support.defaultmessagesourceresolvable: codes [validvo.email,email]; arguments []; default message [email],[ljavax.validation.constraints.pattern$flag;@26139123,.*]; default message [不是一个合法的电子邮件地址]]...", "data": null, "timestamp": 1628239624332 }
调用test2方法
提示的是org.springframework.validation.bindexception异常
post http://localhost:8080/valid/test2 content-type: application/x-www-form-urlencoded id=1&level=12&email=476938977&appid=ab1c
{ "status": 500, "message": "org.springframework.validation.beanpropertybindingresult: 3 errors\nfield error in object 'validvo' on field 'name': rejected value [null]; codes [notblank.validvo.name,notblank.name,notblank.java.lang.string,notblank]; arguments [org.springframework.context.support.defaultmessagesourceresolvable: codes [validvo.name,name]; arguments []; default message [name]]; default message [名字为必填项]...", "data": null, "timestamp": 1628239301951 }
调用test3方法,
提示的是javax.validation.constraintviolationexception异常
post http://localhost:8080/valid/test3 content-type: application/x-www-form-urlencoded email=476938977
{ "status": 500, "message": "test3.email: 不是一个合法的电子邮件地址", "data": null, "timestamp": 1628239281022 }
通过加入 validator
校验框架可以帮助我们自动实现参数的校验。
参数异常加入全局异常处理器
虽然我们之前定义了全局异常拦截器,也看到了拦截器确实生效了,但是 validator
校验框架返回的错误提示太臃肿了,不便于阅读,为了方便前端提示,我们需要将其简化一下。
直接修改之前定义的 restexceptionhandler
,单独拦截参数校验的三个异常:
javax.validation.constraintviolationexception,
org.springframework.validation.bindexception,
org.springframework.web.bind.methodargumentnotvalidexception,
代码如下:
@exceptionhandler(value = {bindexception.class, validationexception.class, methodargumentnotvalidexception.class}) public responseentity<resultdata<string>> handlevalidatedexception(exception e) { resultdata<string> resp = null; if (e instanceof methodargumentnotvalidexception) { // beanvalidation exception methodargumentnotvalidexception ex = (methodargumentnotvalidexception) e; resp = resultdata.fail(httpstatus.bad_request.value(), ex.getbindingresult().getallerrors().stream() .map(objecterror::getdefaultmessage) .collect(collectors.joining("; ")) ); } else if (e instanceof constraintviolationexception) { // beanvalidation get simple param constraintviolationexception ex = (constraintviolationexception) e; resp = resultdata.fail(httpstatus.bad_request.value(), ex.getconstraintviolations().stream() .map(constraintviolation::getmessage) .collect(collectors.joining("; ")) ); } else if (e instanceof bindexception) { // beanvalidation get object param bindexception ex = (bindexception) e; resp = resultdata.fail(httpstatus.bad_request.value(), ex.getallerrors().stream() .map(objecterror::getdefaultmessage) .collect(collectors.joining("; ")) ); } return new responseentity<>(resp,httpstatus.bad_request); }
体验效果
post http://localhost:8080/valid/test1 content-type: application/json { "id": 1, "level": "12", "email": "47693899", "appid": "ab1c" }
post http://localhost:8080/valid/test1 content-type: application/json { "id": 1, "level": "12", "email": "47693899", "appid": "ab1c" }
{ "status": 400, "message": "名字为必填项; 不是一个合法的电子邮件地址; appid长度必须位于6到12之间", "data": null, "timestamp": 1628435116680 }
是不是感觉清爽多了?
自定义参数校验
虽然spring validation 提供的注解基本上够用,但是面对复杂的定义,我们还是需要自己定义相关注解来实现自动校验。
比如上面实体类中的sex性别属性,只允许前端传递传 m,f 这2个枚举值,如何实现呢?
第一步,创建自定义注解
@target({method, field, annotation_type, constructor, parameter, type_use}) @retention(runtime) @repeatable(enumstring.list.class) @documented @constraint(validatedby = enumstringvalidator.class)//标明由哪个类执行校验逻辑 public @interface enumstring { string message() default "value not in enum values."; class<?>[] groups() default {}; class<? extends payload>[] payload() default {}; /** * @return date must in this value array */ string[] value(); /** * defines several {@link enumstring} annotations on the same element. * * @see enumstring */ @target({method, field, annotation_type, constructor, parameter, type_use}) @retention(runtime) @documented @interface list { enumstring[] value(); } }
第二步,自定义校验逻辑
public class enumstringvalidator implements constraintvalidator<enumstring, string> { private list<string> enumstringlist; @override public void initialize(enumstring constraintannotation) { enumstringlist = arrays.aslist(constraintannotation.value()); } @override public boolean isvalid(string value, constraintvalidatorcontext context) { if(value == null){ return true; } return enumstringlist.contains(value); } }
第三步,在字段上增加注解
@apimodelproperty(value = "性别") @enumstring(value = {"f","m"}, message="性别只允许为f或m") private string sex;
第四步,体验效果
post http://localhost:8080/valid/test2 content-type: application/x-www-form-urlencoded id=1&name=javadaily&level=12&email=476938977@qq.com&appid=ab1cdddd&sex=n
{ "status": 400, "message": "性别只允许为f或m", "data": null, "timestamp": 1628435243723 }
分组校验
一个vo对象在新增的时候某些字段为必填,在更新的时候又非必填。如上面的 validvo
中 id 和 appid 属性在新增操作时都是非必填,而在编辑操作时都为必填,name在新增操作时为必填,面对这种场景你会怎么处理呢?
在实际开发中我见到很多同学都是建立两个vo对象,validcreatevo
,valideditvo
来处理这种场景,这样确实也能实现效果,但是会造成类膨胀,而且极其容易被开发老鸟们嘲笑。
其实 validator
校验框架已经考虑到了这种场景并且提供了解决方案,就是分组校验,只不过很多同学不知道而已。要使用分组校验,只需要三个步骤:
第一步:定义分组接口
public interface validgroup extends default { interface crud extends validgroup{ interface create extends crud{ } interface update extends crud{ } interface query extends crud{ } interface delete extends crud{ } } }
这里我们定义一个分组接口validgroup让其继承 javax.validation.groups.default
,再在分组接口中定义出多个不同的操作类型,create,update,query,delete。至于为什么需要继承default我们稍后再说。
第二步,在模型中给参数分配分组
@data @apimodel(value = "参数校验类") public class validvo { @apimodelproperty("id") @null(groups = validgroup.crud.create.class) @notnull(groups = validgroup.crud.update.class, message = "应用id不能为空") private string id; @null(groups = validgroup.crud.create.class) @notnull(groups = validgroup.crud.update.class, message = "应用id不能为空") @apimodelproperty(value = "应用id",example = "cloud") private string appid; @apimodelproperty(value = "名字") @notblank(groups = validgroup.crud.create.class,message = "名字为必填项") private string name; @apimodelproperty(value = "邮箱") @email(message = "请填写正取的邮箱地址") privte string email; ... }
给参数指定分组,对于未指定分组的则使用的是默认分组。
第三步,给需要参数校验的方法指定分组
@restcontroller @api("参数校验") @slf4j @validated public class validcontroller { @apioperation("新增") @postmapping(value = "/valid/add") public string add(@validated(value = validgroup.crud.create.class) validvo validvo){ log.info("validentity is {}", validvo); return "test3 valid success"; } @apioperation("更新") @postmapping(value = "/valid/update") public string update(@validated(value = validgroup.crud.update.class) validvo validvo){ log.info("validentity is {}", validvo); return "test4 valid success"; } }
这里我们通过 value
属性给 add()
和 update()
方法分别指定create和update分组。
第四步,体验效果
post http://localhost:8080/valid/add content-type: application/x-www-form-urlencoded name=javadaily&level=12&email=476938977@qq.com&sex=f
在create时我们没有传递id和appid参数,校验通过。
当我们使用同样的参数调用update方法时则提示参数校验错误。
{ "status": 400, "message": "id不能为空; 应用id不能为空", "data": null, "timestamp": 1628492514313 }
由于email属于默认分组,而我们的分组接口 validgroup
已经继承了 default
分组,所以也是可以对email字段作参数校验的。如:
post http://localhost:8080/valid/add content-type: application/x-www-form-urlencoded name=javadaily&level=12&email=476938977&sex=f
{ "status": 400, "message": "请填写正取的邮箱地址", "data": null, "timestamp": 1628492637305 }
当然如果你的validgroup没有继承default分组,那在代码属性上就需要加上
@validated(value = {validgroup.crud.create.class, default.class}
才能让 email
字段的校验生效。
小结
参数校验在实际开发中使用频率非常高,但是很多同学还只是停留在简单的使用上,像分组校验,自定义参数校验这2个高阶技巧基本没怎么用过,经常出现譬如建立多个vo用于接受create,update场景的情况,很容易被老鸟被所鄙视嘲笑,希望大家好好掌握。
github地址:https://github.com/jianzh5/cloud-blog/
以上就是springboot参数校验过程示例教程的详细内容,更多关于springboot参数校验的资料请关注其它相关文章!