浅谈自定义校验注解ConstraintValidator

2025-05-29 0 92

一、前言

系统执行业务逻辑之前,会对输入数据进行校验,检测数据是否有效合法的。所以我们可能会写大量的if else等判断逻辑,特别是在不同方法出现相同的数据时,校验的逻辑代码会反复出现,导致代码冗余,阅读性和可维护性极差。

jsr-303是java为bean数据合法性校验提供的标准框架,它定义了一整套校验注解,可以标注在成员变量,属性方法等之上。

hibernate-validator就提供了这套标准的实现,我们在用springboot开发web应用时,会引入spring-boot-starter-web依赖,它默认会引入spring-boot-starter-validation依赖,而spring-boot-starter-validation中就引用了hibernate-validator依赖。

浅谈自定义校验注解ConstraintValidator

但是,在比较高版本的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的资料请关注快网idc其它相关文章!

原文链接:https://www.cnblogs.com/luciochn/p/14529281.html

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 浅谈自定义校验注解ConstraintValidator https://www.kuaiidc.com/106173.html

相关文章

发表评论
暂无评论