如何在springboot中使用jsr303对后端数据进行校验?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
项目创建
首先创建一个springboot项目
使用的springboot版本为:(本文代码以该版本为准,不同版本springboot,在下面内容会出现一些差异)
org.springframework.boot spring-boot-starter-parent 2.3.9.release
引入如下依赖
org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-starter-validation
这个作标在新一点的springboot版本中,需要单独引入。在老版本是默认引入的。这个是用来引入对jsr303注解的支持。
org.springframework.boot spring-boot-starter-validation
接着创建一个java bean
package cn.jxj4869.demo.entity; import lombok.data; import javax.validation.constraints.notnull; @data public class user { @notnull private integer id; private string username; private string password; private string email; }
返回类型的javabean
package cn.jxj4869.demo.entity; import java.util.hashmap; public class r extends hashmap{ private static final long serialversionuid = 1l; public r() { put("code", 0); put("msg", "success"); } public static r error(int code, string msg) { r r = new r(); r.put("code", code); r.put("msg", msg); return r; } public static r ok(string msg) { r r = new r(); r.put("msg", msg); return r; } public r put(string key, object value) { super.put(key, value); return this; } }
创建一个controller。
index方法用来跳转到凯发真人娱乐首页。
package cn.jxj4869.demo.controller; import org.springframework.stereotype.controller; import org.springframework.web.bind.annotation.*; @controller public class usercontroller { @requestmapping("/") public string index(){ return "index"; } }
凯发真人娱乐首页代码放到resources/templates
目录下
title 新增表单
更新表单
传统的检验方式
要在后端进行数据校验,传统的校验方式在controller层接受数据后,按照要求对数据进行校验
比如要接收一个user bean对象。
现在要对user对象中的username
属性进行非空校验,password
属性进行非空校验和长度校验。
@postmapping("/user") @responsebody public r user1(user user) throws exception { if(stringutils.isempty(user.getusername())) { return r.error(400,"username不能为空"); } if(stringutils.isempty(user.getpassword())||user.getpassword().length()>8||user.getpassword().length() <4) { return r.error(400,"password无效"); } return null; }
如果有多个方法都需要接受user对象, 而且要校验的属性可能不止username
和password
这两个属性,如果每个方法里面都采用上面这种校验方式的话,代码就会很臃肿,而且不好维护,当改变了userbean的属性,或者对校验规则进行修改后,就得对所有的校验代码进行更新。 这是一件工程量很大的事。
使用jsr303
为了解决上述问题,我们可以使用jsr303提供的注解进行校验。
jsr是java specification requests的缩写,意思是java 规范提案。jsr303也就是第303号提案。
使用jsr303的方法很简单,例如上面的需求,我们只需要在user的属性上加上注解即可。
步骤如下:
1、给bean添加校验注解,一般是在 javax.validation.constraints
这个包下,也还有一些是hibernate
提供的。
2、开启校验功能@valid。
3、当校验失败的时候,会抛出org.springframework.validation.bindexception
异常。
常用的校验注解在文末
package cn.jxj4869.demo.entity; import lombok.data; import org.hibernate.validator.constraints.length; import javax.validation.constraints.notnull; @data public class user { private integer id; @notblank private string username; @notblank @length(min = 4,max = 8) private string password; private string email; }
然后在controller里面的方法上,加上@valid
注解即可
@postmapping("/user2") @responsebody public r user2(@valid user user) throws exception { system.out.println(user); return null; }
当校验失败后,会出现如下错误。并且会给出默认的提示信息。
自定义错误信息
那这个错误信息是怎么来的呢。
进入@notnull
注解的代码里面
@target({ method, field, annotation_type, constructor, parameter, type_use }) @retention(runtime) @repeatable(list.class) @documented @constraint(validatedby = { }) public @interface notblank { string message() default "{javax.validation.constraints.notnull.message}"; ............ ............ ............ }
会有一个message
属性。显然就是指定错误的提示内容。而这些错误提示是在一个叫validationmessages.properties
的文件中,用idea的搜索工具可以找到,双击shift
键打开搜索。
发现有这么多validationmessages.properties
的文件,而且支持国际化。
打开validationmessages_zh.properties
,可以看到里面定义了这么多的提示。而错误提示就是从这文件中获取的。
如果我们不想用默认的校验提示信息的话,可以自己指定。
指定message的值即可。
@notblank(message = "用户名不能为空") private string username;
错误信息的获取与响应
当校验出错时,会默认返回一个错误界面,或者返回错误提示的json数据。但默认提供的显然不是我们想要的,如果可以拿到错误信息,那我们就能自定义相应数据了。
拿到错误信息的方式也很简单,只要在方法中加上bindingresult result
这个参数,错误信息就会封装这里面。
@postmapping("/user2") @responsebody public r user2(@valid user user, bindingresult result) throws exception { system.out.println(user); if(result.haserrors()) { //判断是否有错误 mapmap = new hashmap<>(); //1、获取校验的错误结果 result.getfielderrors().foreach((item)->{ //fielderror 获取到错误提示 string message = item.getdefaultmessage(); //获取错误的属性的名字 string field = item.getfield(); map.put(field,message); }); return r.error(400,"提交的数据不合法").put("data",map); } // 若没有错误,则进行接下去的业务操作。 return null; }
不过不推荐上面这种方式,理由同上,当校验的地方多了,每个方法里面都加上这么个异常处理,会让代码很臃肿。
不知道你们是否还记得,springmvc里面有个全局的异常处理,我们可以自定义一个异常处理,在这里面统一处理异常。
统一处理binexception
。这样就可以不用在controller中去处理错误信息了。
package cn.jxj4869.demo.execption; import cn.jxj4869.demo.entity.r; import org.springframework.validation.bindexception; import org.springframework.web.bind.annotation.exceptionhandler; import org.springframework.web.bind.annotation.restcontrolleradvice; import java.util.hashmap; import java.util.map; @restcontrolleradvice(basepackages = "cn.jxj4869.demo.controller") public class myexceptioncontrolleradvice { @exceptionhandler(value = bindexception.class) public r handlevaildexception(bindexception e) { mapmap = new hashmap<>(); //1、获取校验的错误结果 e.getfielderrors().foreach((item)->{ //fielderror 获取到错误提示 string message = item.getdefaultmessage(); //获取错误的属性的名字 string field = item.getfield(); map.put(field,message); }); return r.error(400,"提交的数据不合法").put("data",map); } }
错误异常类型补充
校验出错的时候,会抛出两种异常
org.springframework.validation.bindexception
使用@valid
注解进行校验的时候抛出的
org.springframework.web.bind.methodargumentnotvalidexception
使用@validated
校验的时候抛出的
在异常捕获中加入下面这个
@exceptionhandler(value= methodargumentnotvalidexception.class) public r handlevaildexception(methodargumentnotvalidexception e){ bindingresult bindingresult = e.getbindingresult(); mapmap = new hashmap<>(); bindingresult.getfielderrors().foreach((fielderror)->{ map.put(fielderror.getfield(),fielderror.getdefaultmessage()); }); return r.error(400,"提交的数据不合法").put("data",map); }
分组校验
在不同业务场景下,校验规则是不一样的,比如user对象中id
这个属性,在新增的时候,这个属性是不用填的,要为null,但是在修改的时候,id
属性是不能为null的。
可以用注解中的groups
属性来指定,在什么场合下使用改注解
@documented @constraint(validatedby = { }) @target({ method, field, annotation_type, constructor, parameter, type_use }) @retention(runtime) @repeatable(list.class) public @interface notblank { class[] groups() default { }; ........ }
首先定义两个接口addgroup
和updategroup
,不需要做任何实现
package cn.jxj4869.demo.valid; public interface updategroup { }
package cn.jxj4869.demo.valid; public interface addgroup { }
在user中指定group。
-
id属性在addgroup的时候,要为null,在updategroup的时候不能为null
-
username属性在addgroup和update的时候,都要进行校验,不能为空。
-
password属性,当校验的时候指定分组的话,会不起作用,因为没有给它指定校验的分组
package cn.jxj4869.demo.entity; import cn.jxj4869.demo.valid.addgroup; import cn.jxj4869.demo.valid.updategroup; import lombok.data; import javax.validation.constraints.notblank; import javax.validation.constraints.notempty; import javax.validation.constraints.notnull; import javax.validation.constraints.null; @data public class user { @null(groups = {addgroup.class}) @notnull(groups = {updategroup.class}) private integer id; @notblank(message = "用户名不能为空",groups = {addgroup.class,updategroup.class}) private string username; @notempty private string password; private string email; }
在controller中用@validated
注解,指定校验的分组
@postmapping("/user3") @responsebody public r user3(@validated(updategroup.class) user user) { system.out.println(user); return null; }
结果如下图所示,因为password
属性没有指定校验的分组,所以在校验的时候,都不会对它进行合法性检查。
自定义校验
当提供的注解不能满足我们需求的时候,可以自定义注解。
例如我们现在给user新加一个属性status
,并要求这个属性的值只能是0或者1。
新建一个@statusvalue
注解。
根据jsr303的规范,校验注解得有三个属性。
-
message
:用来获取错误提示的 -
groups
:指定校验分组的。 -
payload:可以自定义一些负载信息
使用@constraint
注解指定该注解的校验器
package cn.jxj4869.demo.valid; import javax.validation.constraint; import javax.validation.payload; import java.lang.annotation.documented; import java.lang.annotation.retention; import java.lang.annotation.target; import static java.lang.annotation.elementtype.*; import static java.lang.annotation.elementtype.type_use; import static java.lang.annotation.retentionpolicy.runtime; @documented @constraint(validatedby = { statusvalueconstraintvalidator.class }) @target({ method, field, annotation_type, constructor, parameter, type_use }) @retention(runtime) @interface statusvalue { string message() default "{cn.jxj4869.valid.statusvalue.message}"; class[] groups() default { }; class[] payload() default { }; int[] value() default { }; }
自定义校验器
需要实现constraintvalidator
这个接口,第一个泛型是表示要校验哪个注解,第二个泛型是要校验的数据的类型。
-
initialize
是初始化方法 -
isvalid
校验方法,判断是否校验成功
package cn.jxj4869.demo.valid; import javax.validation.constraintvalidator; import javax.validation.constraintvalidatorcontext; import java.util.hashset; import java.util.set; public class statusvalueconstraintvalidator implements constraintvalidator{ private set set = new hashset<>(); //初始化方法 @override public void initialize(statusvalue constraintannotation) { int[] value = constraintannotation.value(); for (int val : value) { set.add(val); } } /** * 判断是否校验成功 * @param value * @param context * @return */ @override public boolean isvalid(integer value, constraintvalidatorcontext context) { return set.contains(value); } }
最后在resources
目录下添加一个validationmessages.properties
文件
用来指定错误信息。
cn.jxj4869.valid.statusvalue.message=必须提交指定的值
userbean
@data public class user { @null(groups = {addgroup.class}) @notnull(groups = {updategroup.class}) private integer id; @notblank(message = "用户名不能为空",groups = {addgroup.class,updategroup.class}) private string username; @notempty private string password; private string email; @statusvalue(value = {0,1},groups = {addgroup.class,updategroup.class}) private integer status; }
常用注解汇总
注解 | 功能 |
---|---|
@null | 对象必须为null |
@notnull | 对象必须不为null,无法检查长度为0的字符串 |
@notblank | 字符串必须不为null,且去掉前后空格长度必须大于0 |
@notempty | 字符串必须非空 |
@length(min = 1,max = 50) | 字符串必须在指定长度内 |
@range(min = 0,max = 100) | 必须在指定范围内 |
@asserttrue | 对象必须为true |
@assertfalse | 对象必须为false |
@max(value) | 必须为数字,且小于或等于value |
@min(value) | 必须为数字,且大于或等于value |
@decimalmax(value) | 必须为数字( bigdecimal ),且小于或等于value。小数存在精度 |
@decimalmin(value) | 必须为数字( bigdecimal ),且大于或等于value。小数存在精度 |
@digits(integer,fraction) | 必须为数字( bigdecimal ),integer整数精度,fraction小数精度 |
@size(min,max) | 对象(array、collection、map、string)长度必须在给定范围 |
字符串必须是合法邮件地址 | |
@past | date和calendar对象必须在当前时间之前 |
@future | date和calendar对象必须在当前时间之后 |
@pattern(regexp=“正则”) | 字符串满足正则表达式的值 |
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注本站行业资讯频道,感谢您对本站的支持。