好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

浅谈自定义校验注解ConstraintValidator

一、前言

系统执行业务逻辑之前,会对输入数据进行校验,检测数据是否有效合法的。所以我们可能会写大量的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

查看更多关于浅谈自定义校验注解ConstraintValidator的详细内容...

  阅读:19次