一、前言
系统执行业务逻辑之前,会对输入数据进行校验,检测数据是否有效合法的。所以我们可能会写大量的if else等判断逻辑,特别是在不同方法出现相同的数据时,校验的逻辑代码会反复出现,导致代码冗余,阅读性和可维护性极差。
jsr-303是java为bean数据合法性校验提供的标准框架,它定义了一整套校验注解,可以标注在成员变量,属性方法等之上。
hibernate-validator就提供了这套标准的实现,我们在用springboot开发web应用时,会引入spring-boot-starter-web依赖,它默认会引入spring-boot-starter-validation依赖,而spring-boot-starter-validation中就引用了hibernate-validator依赖。
但是,在比较高版本的spring-boot-starter-web中,默认不再引用spring-boot-starter-validation,自然也就不会默认引入到hibernate-validator依赖,需要我们手动添加依赖。
1
2
3
4
5
|
<dependency> <groupid>org.hibernate.validator</groupid> <artifactid>hibernate-validator</artifactid> <version> 6.1 . 7 . final </version> </dependency> |
hibernate-validator中有很多非常简单好用的校验注解,例如notnull,@notempty,@min,@max,@email,@positiveorzero等等。这些注解能解决我们大部分的数据校验问题。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.nobody.dto; import lombok.data; import javax.validation.constraints.*; @data public class userdto { @notblank (message = "姓名不能为空" ) private string name; @min (value = 18 , message = "年龄不能小于18" ) private int age; @notempty (message = "邮箱不能为空" ) @email (message = "邮箱格式不正确" ) private string email; } |
二、自定义参数校验器
但是,hibernate-validator中的这些注解不一定能满足我们全部的需求,我们想校验的逻辑比这复杂。所以,我们可以自定义自己的参数校验器。
首先引入依赖是必不可少的。
1
2
3
4
5
|
<dependency> <groupid>org.hibernate.validator</groupid> <artifactid>hibernate-validator</artifactid> <version> 6.1 . 7 . final </version> </dependency> |
最近不是基金很火吗,一大批的韭菜疯狂地涌入买基金的浪潮中。我就以用户开户为例,首先要校验此用户是不是成年人(即不能小于18岁),以及名字是不是以"新韭菜"开头的,符合条件的才允许开户。
定义一个注解,用于校验用户的姓名是不是以“新韭菜”开头的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
package com.nobody.annotation; import com.nobody.validator.isleekvalidator; import javax.validation.constraint; import javax.validation.payload; import java.lang.annotation.*; @retention (retentionpolicy.runtime) @target (elementtype.field) @documented @constraint (validatedby = isleekvalidator. class ) // 指定我们自定义的校验类 public @interface isleek { /** * 是否强制校验 * * @return 是否强制校验的boolean值 */ boolean required() default true ; /** * 校验不通过时的报错信息 * * @return 校验不通过时的报错信息 */ string message() default "此用户不是韭零后,无法开户!" ; /** * 将validator进行分类,不同的类group中会执行不同的validator操作 * * @return validator的分类类型 */ class <?>[] groups() default {}; /** * 主要是针对bean,很少使用 * * @return 负载 */ class <? extends payload>[] payload() default {}; } |
定义校验类,实现constraintvalidator接口,接口使用了泛型,需要指定两个参数,第一个是自定义注解,第二个是需要校验的数据类型。重写2个方法,initialize方法主要做一些初始化操作,它的参数是我们使用到的注解,可以获取到运行时的注解信息。isvalid方法就是要实现的校验逻辑,被注解的对象会传入此方法中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package com.nobody.validator; import com.nobody.annotation.isleek; import org.springframework.util.stringutils; import javax.validation.constraintvalidator; import javax.validation.constraintvalidatorcontext; public class isleekvalidator implements constraintvalidator<isleek, string> { // 是否强制校验 private boolean required; @override public void initialize(isleek constraintannotation) { this .required = constraintannotation.required(); } @override public boolean isvalid(string name, constraintvalidatorcontext constraintvalidatorcontext) { if (required) { // 名字以"新韭菜"开头的则校验通过 return !stringutils.isempty(name) && name.startswith( "新韭菜" ); } return false ; } } |
三、使用自定义注解
通过以上几个步骤,我们自定义的校验注解就完成了,我们使用测试下效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package com.nobody.dto; import com.nobody.annotation.isleek; import lombok.data; import javax.validation.constraints.*; @data public class userdto { @notblank (message = "姓名不能为空" ) @isleek // 我们自定义的注解 private string name; @min (value = 18 , message = "年龄不能小于18" ) private int age; @notempty (message = "邮箱不能为空" ) @email (message = "邮箱格式不正确" ) private string email; } |
写个接口,模拟用户开户业务,调用测试。注意,记得加上@valid注解开启校验,不然不生效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package com.nobody.controller; import com.nobody.dto.userdto; import org.springframework.web.bind.annotation.postmapping; import org.springframework.web.bind.annotation.requestbody; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; import javax.validation.valid; @restcontroller @requestmapping ( "user" ) public class usercontroller { @postmapping ( "add" ) public userdto add( @requestbody @valid userdto userdto) { system.out.println( ">>> 用户开户成功..." ); return userdto; } } |
如果参数校验不通过,会抛出methodargumentnotvalidexception异常,我们全局处理下然后返回给接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.nobody.exception; import javax.servlet.http.httpservletrequest; import org.springframework.web.bind.methodargumentnotvalidexception; import org.springframework.web.bind.annotation.controlleradvice; import org.springframework.web.bind.annotation.exceptionhandler; import org.springframework.web.bind.annotation.responsebody; import lombok.extern.slf4j.slf4j; @controlleradvice @slf4j public class globalexceptionhandler { // 处理接口参数数据格式错误异常 @exceptionhandler (value = methodargumentnotvalidexception. class ) @responsebody public object errorhandler(httpservletrequest request, methodargumentnotvalidexception e) { return e.getbindingresult().getallerrors(); } } |
我们先测试用户姓名不带"新韭菜"前缀的进行测试,发现校验不通过,证明注解生效了。
post http://localhost:8080/user/add
content-type: application/json
{"name": "小绿", "age": 19, "email": "845136542@qq.com"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
[ { "codes" : [ "isleek.userdto.name" , "isleek.name" , "isleek.java.lang.string" , "isleek" ], "arguments" : [ { "codes" : [ "userdto.name" , "name" ], "arguments" : null , "defaultmessage" : "name" , "code" : "name" }, true ], "defaultmessage" : "此用户不是韭零后,无法开户!" , "objectname" : "userdto" , "field" : "name" , "rejectedvalue" : "小绿" , "bindingfailure" : false , "code" : "isleek" } |
如果多个参数校验失败,报错信息也都能获得。如下所示,姓名和邮箱都校验失败。
post http://localhost:8080/user/add
content-type: application/json
{"name": "小绿", "age": 19, "email": "84513654"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
[ { "codes" : [ "email.userdto.email" , "email.email" , "email.java.lang.string" , "email" ], "arguments" : [ { "codes" : [ "userdto.email" , "email" ], "arguments" : null , "defaultmessage" : "email" , "code" : "email" }, [], { "defaultmessage" : ".*" , "codes" : [ ".*" ], "arguments" : null } ], "defaultmessage" : "邮箱格式不正确" , "objectname" : "userdto" , "field" : "email" , "rejectedvalue" : "84513654" , "bindingfailure" : false , "code" : "email" }, { "codes" : [ "isleek.userdto.name" , "isleek.name" , "isleek.java.lang.string" , "isleek" ], "arguments" : [ { "codes" : [ "userdto.name" , "name" ], "arguments" : null , "defaultmessage" : "name" , "code" : "name" }, true ], "defaultmessage" : "此用户不是韭零后,无法开户!" , "objectname" : "userdto" , "field" : "name" , "rejectedvalue" : "小绿" , "bindingfailure" : false , "code" : "isleek" } ] |
以下是所有参数校验通过的情况:
post http://localhost:8080/user/add
content-type: application/json
{"name": "新韭菜小绿", "age": 19, "email": "84513654@qq.com"}
{
"name": "新韭菜小绿",
"age": 19,
"email": "84513654@qq.com"
}
我们可能会将userdto对象用在不同的接口中接收参数,比如在新增和修改接口中。在新增接口中,不需要校验userid;在修改接口中需要校验userid。那注解中的groups字段就派上用场了。groups和@validated配合能控制哪些注解需不需要开启校验。
我们首先定义2个groups分组接口update和create,并且继承default接口。当然也可以不继承default接口,因为使用注解时不显示指定groups的值,则默认为groups = {default.class}。所以继承了default接口,在用@validated(create.class)时,也会校验groups = {default.class}的注解。
1
2
3
4
5
6
|
package com.nobody.annotation; import javax.validation.groups. default ; public interface create extends default { } |
1
2
3
4
5
6
|
package com.nobody.annotation; import javax.validation.groups. default ; public interface update extends default { } |
在用到注解的地方,填写groups的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.nobody.dto; import com.nobody.annotation.create; import com.nobody.annotation.isleek; import com.nobody.annotation.update; import lombok.data; import javax.validation.constraints.*; @data public class userdto { @notblank (message = "用户id不能为空" , groups = update. class ) private string userid; @notblank (message = "姓名不能为空" , groups = {update. class , create. class }) @isleek private string name; @min (value = 18 , message = "年龄不能小于18" ) private int age; @notempty (message = "邮箱不能为空" ) @email (message = "邮箱格式不正确" ) private string email; } |
最后,在需要声明校验的地方,通过@validated的指定即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package com.nobody.controller; import com.nobody.annotation.create; import com.nobody.annotation.update; import com.nobody.dto.userdto; import org.springframework.validation.annotation.validated; import org.springframework.web.bind.annotation.postmapping; import org.springframework.web.bind.annotation.requestbody; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; @restcontroller @requestmapping ( "user" ) public class usercontroller { @postmapping ( "add" ) public object add( @requestbody @validated (create. class ) userdto userdto) { system.out.println( ">>> 用户开户成功..." ); return userdto; } @postmapping ( "update" ) public object update( @requestbody @validated (update. class ) userdto userdto) { system.out.println( ">>> 用户信息修改成功..." ); return userdto; } } |
调用add接口时,即使不传userid也能通过,即不对userid进行校验。
post http://localhost:8080/user/add
content-type: application/json
{"name": "新韭菜小绿", "age": 18, "email": "84513654@qq.com"}
调用update接口时,不传userid,会校验不通过。
post http://localhost:8080/user/update
content-type: application/json
{"name": "新韭菜小绿", "age": 18, "email": "84513654@qq.com"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
[ { "codes" : [ "notblank.userdto.userid" , "notblank.userid" , "notblank.java.lang.string" , "notblank" ], "arguments" : [ { "codes" : [ "userdto.userid" , "userid" ], "arguments" : null , "defaultmessage" : "userid" , "code" : "userid" } ], "defaultmessage" : "用户id不能为空" , "objectname" : "userdto" , "field" : "userid" , "rejectedvalue" : null , "bindingfailure" : false , "code" : "notblank" } ] |
此演示项目已上传到github,如有需要可自行下载,欢迎 star 。 https://github.com/luciochn/spring
以上就是浅谈自定义校验注解constraintvalidator的详细内容,更多关于自定义校验注解constraintvalidator的资料请关注服务器之家其它相关文章!
原文链接:https://www.cnblogs.com/luciochn/p/14529281.html