1. 簡介:
通過使用注解annotations 給類或者類的屬性加上約束(constraint),在運行期檢查屬性值的合法性.
2. 作用:
在api接口開發中參數校驗是非常重要的事情,因為客戶端很可能會少傳參數,或者值不合法,甚至參數值是惡意的,所以對客戶端傳來的參數的合法性就必須要校驗了,其中將參數值的校驗規則通過注解的形式注解到屬性上是一種比較優雅的方式。
3. 常用的約束注解
- @null 被注釋的元素必須為 null
- @notnull 被注釋的元素必須不為 null
- @asserttrue 被注釋的元素必須為 true
- @assertfalse 被注釋的元素必須為 false
- @min(value) 被注釋的元素必須是一個數字,其值必須大于等于指定的最小值
- @max(value) 被注釋的元素必須是一個數字,其值必須小于等于指定的最大值
- @decimalmin(value) 被注釋的元素必須是一個數字,其值必須大于等于指定的最小值
- @decimalmax(value) 被注釋的元素必須是一個數字,其值必須小于等于指定的最大值
- @size(max=, min=) 被注釋的元素的大小必須在指定的范圍內
- @digits (integer, fraction) 被注釋的元素必須是一個數字,其值必須在可接受的范圍內
- @past 被注釋的元素必須是一個過去的日期
- @future 被注釋的元素必須是一個將來的日期
- @pattern(regex=,flag=) 被注釋的元素必須符合指定的正則表達式
- hibernate validator 附加的 constraint
- @notblank(message =) 驗證字符串非null,且長度必須大于0
- @email 被注釋的元素必須是電子郵箱地址
- @length(min=,max=) 被注釋的字符串的大小必須在指定的范圍內
- @notempty 被注釋的字符串的必須非空
- @range(min=,max=,message=) 被注釋的元素必須在合適的范圍內
- @url(protocol=,host=, port=, regexp=, flags=) 被注釋的字符串必須是一個有效的url
- @creditcardnumber 被注釋的字符串必須通過luhn校驗算法,銀行卡,信用卡等號碼一般都用luhn計算合法性
- @scriptassert(lang=, script=, alias=) 要有java scripting api 即jsr 223 (“scripting for the javatm platform”)的實現
- @safehtml(whitelisttype=, additionaltags=) classpath中要有jsoup包
4. 初識hibernate-validator
1
2
3
4
5
6
7
8
9
10
11
12
|
public class address { @notnull private string line1; private string line2; private string zip; private string state; @length (max = 20 ) @notnull private string country; @range (min = - 2 , max = 50 , message = "floor out of range" ) public int floor; // getter&&setter } |
二:整合校驗 hibernate-validator
該示例是在springmvc+fastjson整合(http://www.jfrwli.cn/article/158724.html)基礎上進行集成的,可以先看一下這篇文章(有源碼供下載),在該文章的基礎上整合hibernate-validator
整合步驟:
1、在pom.xml中引入hibernate-validator依賴
1
2
3
4
5
|
<dependency> <groupid>org.hibernate</groupid> <artifactid>hibernate-validator</artifactid> <version> 5.4 . 1 . final </version> </dependency> |
2、在[xxx]-servlet.xml中配置驗證器:hibernatevalidator
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
|
<!-- <mvc:annotation-driven> 增加驗證器屬性validator= "validator" --> <mvc:annotation-driven validator= "validator" > <mvc:message-converters register-defaults= "true" > <!-- 配置fastjson 替換原來的jackson支持 --> <bean class = "com.alibaba.fastjson.support.spring.fastjsonhttpmessageconverter" > <property name= "supportedmediatypes" > <list> <value>text/html;charset=utf- 8 </value> <value>application/json</value> </list> </property> <property name= "features" > <list> <value>quotefieldnames</value> <value>writemapnullvalue</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id= "validator" class = "org.springframework.validation.beanvalidation.localvalidatorfactorybean" > <property name= "providerclass" value= "org.hibernate.validator.hibernatevalidator" /> <property name= "validationmessagesource" ref= "messagesource" /> </bean> <bean id= "messagesource" class = "org.springframework.context.support.reloadableresourcebundlemessagesource" > <property name= "basenames" > <list> <value>classpath:conf/settings/validation</value> <value>classpath:org/hibernate/validator/validationmessages</value> </list> </property> <property name= "usecodeasdefaultmessage" value= "false" /> <property name= "defaultencoding" value= "utf-8" /> <property name= "cacheseconds" value= "60" /> </bean> |
3、在src/main/resources/conf/settings位置定義驗證消息文件描述 validation.properties
1
2
3
4
5
6
7
|
validation.common.not. null =該字段不能為空 validation.common.not.range=長度非法 validation.common.format.error=格式錯誤 validation.param.age=年齡未滿 18 周歲 rep.error.unknown=未知錯誤 |
4、新建實體類以供測試
userentity
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
package com.mengdee.manage.validator; import javax.validation.constraints.min; import javax.validation.constraints.notnull; import javax.validation.constraints. null ; import javax.validation.constraints.pattern; import org.hibernate.validator.constraints.email; import org.hibernate.validator.constraints.length; import org.hibernate.validator.constraints.notblank; public class userentity { @null (groups={groupa. class }) @notnull (groups={groupb. class }) @min (value = 1 , message= "id值必須大于0" , groups={groupb. class }) private long id; @notblank (groups={groupa. class , groupb. class }) @pattern (regexp= "^(?![0-9]+$)(?![a-za-z]+$)[0-9a-za-z]{6,20}$" , message= "由6-21字母和數字組成,不能是純數字或純英文" , groups={groupa. class , groupb. class }) private string password; @notblank (groups={groupa. class , groupb. class }) @pattern (regexp= "^((13[0-9])|(15[^4,\\d])|(18[0,3-9]))\\d{8}$" , message= "手機號格式不正確" ) private string phone; @notblank (groups={groupb. class }) @length (min= 6 , max= 12 , message= "昵稱長度為6到12位" ) private string nickname; @min (value= 18 , message= "{validation.param.age}" ) private int age; @notblank (groups={groupa. class }) @email (message= "{validation.common.format.error}" ) private string email; @notblank @length (min= 3 , max= 10 , message= "{validation.common.not.range}" ) private string username; public userentity() { } public long getid() { return id; } public void setid( long id) { this .id = id; } public string getnickname() { return nickname; } public void setnickname(string nickname) { this .nickname = nickname; } public int getage() { return age; } public void setage( int age) { this .age = age; } public string getemail() { return email; } public void setemail(string email) { this .email = email; } public string getpassword() { return password; } public void setpassword(string password) { this .password = password; } public string getusername() { return username; } public void setusername(string username) { this .username = username; } public string getphone() { return phone; } public void setphone(string phone) { this .phone = phone; } } |
usermodel
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
package com.mengdee.manage.validator; import javax.validation.constraints.min; import javax.validation.constraints.pattern; import org.hibernate.validator.constraints.email; import org.hibernate.validator.constraints.length; import org.hibernate.validator.constraints.notblank; public class usermodel { @min (value = 1 , message= "id值必須大于0" ) private long id; @notblank @length (min= 6 , max= 12 , message= "昵稱長度為6到12位" ) private string nickname; @min (value= 18 , message= "{validation.param.age}" ) private int age; @notblank @email (message= "{validation.common.format.error}" ) private string email; @notblank @pattern (regexp= "^(?![0-9]+$)(?![a-za-z]+$)[0-9a-za-z]{6,20}$" , message= "由6-21字母和數字組成,不能是純數字或純英文" ) private string password; @notblank @length (min= 3 , max= 10 , message= "{validation.common.not.range}" ) private string username; @notblank @pattern (regexp= "^((13[0-9])|(15[^4,\\d])|(18[0,3-9]))\\d{8}$" , message= "手機號格式不正確" ) private string phone; public usermodel() { } public long getid() { return id; } public void setid( long id) { this .id = id; } public string getnickname() { return nickname; } public void setnickname(string nickname) { this .nickname = nickname; } public int getage() { return age; } public void setage( int age) { this .age = age; } public string getemail() { return email; } public void setemail(string email) { this .email = email; } public string getpassword() { return password; } public void setpassword(string password) { this .password = password; } public string getusername() { return username; } public void setusername(string username) { this .username = username; } public string getphone() { return phone; } public void setphone(string phone) { this .phone = phone; } } |
userdetail :
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
|
package com.mengdee.manage.validator; import org.hibernate.validator.constraints.notblank; public class userdetail { private long id; @notblank private string address; @notblank private string company; public userdetail() { } public long getid() { return id; } public void setid( long id) { this .id = id; } public string getaddress() { return address; } public void setaddress(string address) { this .address = address; } public string getcompany() { return company; } public void setcompany(string company) { this .company = company; } } |
使用validcontroller 進行測試
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
|
package com.mengdee.manage.controller; import javax.servlet.http.httpservletrequest; import javax.validation.valid; import org.springframework.stereotype.controller; import org.springframework.validation.bindingresult; import org.springframework.validation.fielderror; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.bind.annotation.responsebody; import com.mengdee.manage.validator.usermodel; @controller @requestmapping ( "/valid" ) public class validcontroller extends basecontroller { // http://localhost:8081/platform-springmvc-webapp/valid/test?age=18&nickname=mengdee&id=1&email=123@qq.com&password=root123&username=123&phone=18321758957 @requestmapping (value = "/test" , method = requestmethod.get) public @responsebody object validation(httpservletrequest request, @valid usermodel user, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return super .responsejsonerror(fielderror); } return "ok" ; } // 一個方法同時校驗多個對象需要綁定多個結果bindingresult,出現一個@valid就對應后面聲明的一個bindingresult @requestmapping (value = "/test2" , method = requestmethod.get) public @responsebody object validationmore(httpservletrequest request, @valid usermodel user, bindingresult bindingresult, @valid userdetail userdetail, bindingresult bindingresult2){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return super .responsejsonerror(fielderror); } if (bindingresult2.haserrors()) { fielderror fielderror = bindingresult2.getfielderror(); return super .responsejsonerror(fielderror); } return "ok" ; } } |
使用注意:
1、對于多個字段,系統驗證的順序好像和字段聲明的順序是不一樣的,好像是無須的
2、對于每個驗證如果沒有message屬性,系統會使用默認的,如對應@notblank相當于@notblank(message=”不能為空”)
3、對于引用類型如string等類型,一定要結合@notnull、@notempty或者@notblank來配合使用,如果不寫空的約束,經測試,該字段是不參與校驗的,如單獨使用@pattern、@length、@email等是不會進行驗證的,必須要使用空的約束來限制
@min:用于基本數據類型,如int、long等
@notnull: 任何對象的value不能為null
@notempty:集合對象的元素不為0,即集合不為空,也可以用于字符串不為null
@notblank:只能用于字符串不為null,并且字符串trim()以后length要大于0
三:分組校驗@validated
- @valid是屬于javax.validation.valid里的。
- @validated是@valid 的一次封裝,是spring提供的校驗機制使用(org.springframework.validation.annotation.validated) ,@valid不提供分組功能
1. 分組的作用(使用場景):
每個校驗的注解約束都有一個groups屬性,用于指定該約束是屬于哪個組的,這樣在同一個字段上就可以配置多套約束,在使用的時候只需要指定使用那套約束即可,例如對于注冊用戶和修改用戶信息時,注冊時id必須為空,修改用戶信息時id必須不能為空,在使用的時候只需要將這兩種約束分配到不同的組中即可,如添加時使用組a的約束,更新時使用組b的約束
2. 分組就是一個空接口interface
groupa 和 groupb
1
2
3
4
5
6
7
|
package com.mengdee.manage.validator; public interface groupa { } package com.mengdee.manage.validator; public interface groupb { } |
在使用時指定具體使用那套分組的約束@validated({groupa.class})
3. 組序列@groupsequence
默認情況下,不同組別的約束驗證是無序的,組序列就是按照分組的前后順序依次驗證,如先驗證groupa組的約束,再驗證groupb組的約束。如果對組的校驗順序有要求,例如必須先校驗a組再校驗b組,可以使用@groupsequence來定義每個組的順序
使用場景:
(1)第二個組中的約束驗證依賴于一個穩定狀態來運行,而這個穩定狀態是由第一個組來進行驗證的。
(2)某個組的驗證比較耗時,cpu 和內存的使用率相對比較大,最優的選擇是將其放在最后進行驗證。因此,在進行組驗證的時候尚需提供一種有序的驗證方式,這就提出了組序列的概念。
一個組可以定義為其他組的序列,使用它進行驗證的時候必須符合該序列規定的順序。在使用組序列驗證的時候,如果序列前邊的組驗證失敗,則后面的組將不再給予驗證。
使用注解groupsequence定義組序列:groupab
1
2
3
4
|
package com.mengdee.manage.validator; import javax.validation.groupsequence; @groupsequence ({groupa. class , groupb. class }) public interface groupab { |
validationcontroller
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
package com.mengdee.manage.controller; import javax.servlet.http.httpservletrequest; import org.springframework.stereotype.controller; import org.springframework.validation.bindingresult; import org.springframework.validation.fielderror; import org.springframework.validation.annotation.validated; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.bind.annotation.responsebody; import com.mengdee.manage.validator.groupa; import com.mengdee.manage.validator.groupab; import com.mengdee.manage.validator.groupb; import com.mengdee.manage.validator.userentity; @controller @requestmapping ( "/validated" ) public class validationcontroller extends basecontroller { // 校驗時指定了groupa,那么只校驗約束中包含groupa的約束,沒有包含就不校驗,例如對于phone, @notblank指定的分組,而@pattern沒有指定分組,那么只校驗空著一個約束,不校驗手機號格式 // http://localhost:8081/platform-springmvc-webapp/validated/groupa?password=root123&phone=123 @requestmapping (value = "/groupa" , method = requestmethod.get) public @responsebody object groupa(httpservletrequest request, @validated ({groupa. class }) userentity user, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return this .responsejsonerror(fielderror); } return "ok" ; } // http://localhost:8081/platform-springmvc-webapp/validated/groupb?phone=123&password=root123&id=1 @requestmapping (value = "/groupb" , method = requestmethod.get) public @responsebody object groupb(httpservletrequest request, @validated ({groupb. class }) userentity user, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return this .responsejsonerror(fielderror); } return "ok" ; } // groupab // http://localhost:8081/platform-springmvc-webapp/validated/groupab?phone=111&password=root123&nickname=123&email=xxx@qq.com // @validated({groupa.class, groupb.class}):groupa和groupb的關系是或的關系,就像數據庫中的or一樣,只要滿足一個條件就會對該約束進行校驗,同時使用多個組注意多個組之間沒有先后屬性之說,并不是先校驗組a,然后再校驗組b // 因為id的為空和不為空的約束都會進行檢查,所以先注釋掉該屬性 @requestmapping (value = "/groupab" , method = requestmethod.get) public @responsebody object groupab(httpservletrequest request, @validated ({groupa. class , groupb. class }) userentity user, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return this .responsejsonerror(fielderror); } return "ok" ; } // default // http://localhost:8081/platform-springmvc-webapp/default?email=xxx@163.com&age=18 // @validated 如果沒有指定groups則驗證沒有分組的屬性(此時和@valid功能一樣),如果一個字段上有多個約束,都必須沒有指定組,如果部分約束指定的組,部分約束沒有指定約束,那么在使用@validated時不進行檢查的 @requestmapping (value = "/default" , method = requestmethod.get) public @responsebody object defaultgroup(httpservletrequest request, @validated userentity user, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return this .responsejsonerror(fielderror); } return "ok" ; } //localhost:8081/platform-springmvc-webapp/validated/sequence?phone=123&password=root123&email=123&nickname=123 // 對于一個屬性上有多個約束,并且多個約束不都在同一個組,那么在檢查的時候順序是根據groupsequence定義的先后順序來檢查的 @requestmapping (value = "/sequence" , method = requestmethod.get) public @responsebody object sequence(httpservletrequest request, @validated ({groupab. class }) userentity user, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return this .responsejsonerror(fielderror); } return "ok" ; } } |
四:自定義hibernate validation注解
當hibernate validation提供的注解不能滿足需求時,可以自定義校驗約束。
自定義注解約束步驟:
- 創建注解
- 創建注解對應的約束驗證類
- 使用注解
- 測試注解
創建注解 @phone
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.mengdee.manage.validator; import java.lang.annotation.target; import javax.validation.constraint; import javax.validation.payload; import static java.lang.annotation.elementtype.annotation_type; import static java.lang.annotation.elementtype.constructor; import static java.lang.annotation.elementtype.field; import static java.lang.annotation.elementtype.method; import static java.lang.annotation.elementtype.parameter; import static java.lang.annotation.retentionpolicy.runtime; import java.lang.annotation.documented; import java.lang.annotation.retention; @target ({method, field, annotation_type, constructor, parameter }) @retention (runtime) @documented @constraint (validatedby = {phoneconstraint. class }) public @interface phone { string message() default "手機號格式錯誤" ; string regexp() default "^((13[0-9])|(15[^4,\\d])|(18[0,3-9]))\\d{8}$" ; class <?>[] groups() default {}; class <? extends payload>[] payload() default { }; @target ({ method, field, annotation_type, constructor, parameter }) @retention (runtime) @documented public @interface list { phone[] value(); } } |
創建手機號注解對應的約束驗證類phoneconstraint
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.mengdee.manage.validator; import javax.validation.constraintvalidator; import javax.validation.constraintvalidatorcontext; public class phoneconstraint implements constraintvalidator<phone, string> { private string regexp; @override public void initialize(phone phoneannotation) { this .regexp = phoneannotation.regexp(); } @override public boolean isvalid(string value, constraintvalidatorcontext context) { if (value == null ) { return true ; // hv000028: unexpected exception during isvalid call } if (value.matches(regexp)) { return true ; } return false ; } } |
在屬性上使用注解@phone
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
package com.mengdee.manage.validator; import org.hibernate.validator.constraints.notblank; public class userdetail { @notblank @phone private string phone; public userdetail() { } public string getphone() { return phone; } public void setphone(string phone) { this .phone = phone; } } |
測試注解
1
2
3
4
5
6
7
8
9
10
|
// http://localhost:8081/platform-springmvc-webapp/valid/test3?address=123&company=456&phone=123 @requestmapping (value = "/test3" , method = requestmethod.get) public @responsebody object validationcustomannotation(httpservletrequest request, @valid userdetail userdetail, bindingresult bindingresult){ if (bindingresult.haserrors()) { fielderror fielderror = bindingresult.getfielderror(); return super .responsejsonerror(fielderror); } return "ok" ; } |
完整代碼下載:platform-springmvc-webapp.rar
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
參考文章:
- hibernate validation文檔:http://docs.jboss.org/hibernate/validator/5.2/reference/en-us/html_single/#validator-customconstraints:
- 自定義注解:http://www.jfrwli.cn/article/102631.html
- 深入理解java:注解(annotation)自定義注解入門:http://www.jfrwli.cn/article/155482.html
原文鏈接:https://blog.csdn.net/vbirdbest/article/details/72620957