請求參數的校驗是不少新手開發很是容易犯錯,或存在較多改進點的常見場景。比較常見的問題主要表如今如下幾個方面:html
if/else
語句嵌套實現,校驗邏輯晦澀難通,不利於長期維護。 因此,針對上面的問題,建議服務端開發在實現接口的時候,對於請求參數必需要有服務端校驗以保障數據安全與穩定的系統運行。同時,對於參數的校驗實現須要足夠優雅,要知足邏輯易讀、易維護的基本特色。前端
接下來,咱們就在本篇教程中詳細說說,如何優雅地實現Spring Boot服務端的請求參數校驗。java
在開始動手實踐以前,咱們先了解一下接下來咱們將使用的一項標準規範:JSR-303git
什麼是JSR?github
JSR是Java Specification Requests的縮寫,意思是Java 規範提案。是指向JCP(Java Community Process)提出新增一個標準化技術規範的正式請求。任何人均可以提交JSR,以向Java平臺增添新的API和服務。JSR已成爲Java界的一個重要標準。web
JSR-303定義的是什麼標準?spring
JSR-303 是JAVA EE 6 中的一項子規範,叫作Bean Validation,Hibernate Validator 是 Bean Validation 的參考實現 . Hibernate Validator 提供了 JSR 303 規範中全部內置 constraint 的實現,除此以外還有一些附加的 constraint。json
Bean Validation中內置的constraint後端
Hibernate Validator附加的constraint數組
在JSR-303的標準之下,咱們能夠經過上面這些註解,優雅的定義各個請求參數的校驗。更多關於JSR的內容能夠參與官方文檔或參考資料中的引文[1]。
已經瞭解了JSR-303以後,接下來咱們就來嘗試一下,基於此規範如何實現參數的校驗!
讀者能夠拿任何一個使用Spring Boot 2.x構建的提供RESTful API的項目做爲基礎。也可使用Spring Boot 2.x基礎教程:使用Swagger2構建強大的API文檔中構建的實驗工程做爲基礎,您能夠經過下面倉庫中的chapter2-2
目錄取得:
固然,您也能夠根據前文再構建一個做爲複習,也是徹底沒有問題的。
咱們先來作一個簡單的例子,好比:定義字段不能爲Null
。只須要兩步
第一步:在要校驗的字段上添加上@NotNull
註解,具體以下:
@Data
@ApiModel(description="用戶實體")
public class User {
@ApiModelProperty("用戶編號")
private Long id;
@NotNull
@ApiModelProperty("用戶姓名")
private String name;
@NotNull
@ApiModelProperty("用戶年齡")
private Integer age;
}複製代碼
第二步:在須要校驗的參數實體前添加@Valid
註解,具體以下:
@PostMapping("/")
@ApiOperation(value = "建立用戶", notes = "根據User對象建立用戶")
public String postUser(@Valid @RequestBody User user) {
users.put(user.getId(), user);
return "success";
}複製代碼
完成上面配置以後,啓動應用,並用POST請求訪問localhost:8080/users/
接口,body使用一個空對象,{}
。你能夠用Postman等測試工具發起,也可使用curl發起,好比這樣:
curl -X POST \
http://localhost:8080/users/ \
-H 'Content-Type: application/json' \
-H 'Postman-Token: 72745d04-caa5-44a1-be84-ba9c115f4dfb' \
-H 'cache-control: no-cache' \
-d '{
}'複製代碼
不出意外,你能夠獲得以下結果:
{
"timestamp": "2019-10-05T05:45:19.221+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.user.age",
"NotNull.age",
"NotNull.java.lang.Integer",
"NotNull"
],
"arguments": [
{
"codes": [
"user.age",
"age"
],
"arguments": null,
"defaultMessage": "age",
"code": "age"
}
],
"defaultMessage": "不能爲null",
"objectName": "user",
"field": "age",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
},
{
"codes": [
"NotNull.user.name",
"NotNull.name",
"NotNull.java.lang.String",
"NotNull"
],
"arguments": [
{
"codes": [
"user.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "不能爲null",
"objectName": "user",
"field": "name",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"message": "Validation failed for object='user'. Error count: 2",
"path": "/users/"
}複製代碼
其中返回內容的各參數含義以下:
timestamp
:請求時間 status
:HTTP返回的狀態碼,這裏返回400,即:請求無效、錯誤的請求,一般參數校驗不經過均爲400 error
:HTTP返回的錯誤描述,這裏對應的就是400狀態的錯誤描述:Bad Request errors
:具體錯誤緣由,是一個數組類型;由於錯誤校驗可能存在多個字段的錯誤,好比這裏由於定義了兩個參數不能爲Null
,因此存在兩條錯誤記錄信息 message
:概要錯誤消息,返回內容中很容易能夠知道,這裏的錯誤緣由是對user對象的校驗失敗,其中錯誤數量爲2
,而具體的錯誤信息就定義在上面的errors
數組中 path
:請求路徑 請求的調用端在拿到這個規範化的錯誤信息以後,就能夠方便的解析並做出對應的措施以完成本身的業務邏輯了。
在完成了上面的例子以後,咱們還能夠增長一些校驗規則,好比:校驗字符串的長度、校驗數字的大小、校驗字符串格式是否爲郵箱等。下面咱們就來定義一些複雜的校驗定義,好比:
@Data
@ApiModel(description="用戶實體")
public class User {
@ApiModelProperty("用戶編號")
private Long id;
@NotNull
@Size(min = 2, max = 5)
@ApiModelProperty("用戶姓名")
private String name;
@NotNull
@Max(100)
@Min(10)
@ApiModelProperty("用戶年齡")
private Integer age;
@NotNull
@Email
@ApiModelProperty("用戶郵箱")
private String email;
}複製代碼
發起一個能夠出發name
、age
、email
都校驗不經過的請求,好比下面這樣:
curl -X POST \
http://localhost:8080/users/ \
-H 'Content-Type: application/json' \
-H 'Postman-Token: 114db0f0-bdce-4ba5-baf6-01e5104a68a3' \
-H 'cache-control: no-cache' \
-d '{
"name": "abcdefg",
"age": 8,
"email": "aaaa"
}'複製代碼
咱們將獲得以下的錯誤返回:
{
"timestamp": "2019-10-05T06:24:30.518+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.user.name",
"Size.name",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"user.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
},
5,
2
],
"defaultMessage": "個數必須在2和5之間",
"objectName": "user",
"field": "name",
"rejectedValue": "abcdefg",
"bindingFailure": false,
"code": "Size"
},
{
"codes": [
"Min.user.age",
"Min.age",
"Min.java.lang.Integer",
"Min"
],
"arguments": [
{
"codes": [
"user.age",
"age"
],
"arguments": null,
"defaultMessage": "age",
"code": "age"
},
10
],
"defaultMessage": "最小不能小於10",
"objectName": "user",
"field": "age",
"rejectedValue": 8,
"bindingFailure": false,
"code": "Min"
},
{
"codes": [
"Email.user.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"user.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"defaultMessage": ".*",
"codes": [
".*"
],
"arguments": null
}
],
"defaultMessage": "不是一個合法的電子郵件地址",
"objectName": "user",
"field": "email",
"rejectedValue": "aaaa",
"bindingFailure": false,
"code": "Email"
}
],
"message": "Validation failed for object='user'. Error count: 3",
"path": "/users/"
}複製代碼
從errors
數組中的各個錯誤明細中,知道各個字段的defaultMessage
,能夠看到很清晰的錯誤描述。
可能有讀者會問了,個人接口中是定了這麼多。上一篇教程中,不是還教了如何自動生成文檔麼,那麼對於參數的校驗邏輯該如何描述呢?
這裏要分兩種狀況,Swagger自身對JSR-303有必定的支持,可是支持的並那麼完善,並無覆蓋全部的註解的。
好比,上面咱們使用的註解是能夠自動生成的,啓動上面咱們的實驗工程,而後訪問http://localhost:8080/swagger-ui.html
,在Models
不是,咱們能夠看到以下圖所示的內容:
其中:name
和age
字段相比上一篇教程中的文檔描述,多了一些關於校驗相關的說明;而email
字段則沒有體現相關校驗說明。目前,Swagger共支持如下幾個註解:@NotNull
、@Max
、@Min
、@Size
、@Pattern
。在實際開發過程當中,咱們須要分狀況來處理,對於Swagger支自動生成的能夠利用原生支持來產生,若是有部分字段沒法產生,則能夠在@ApiModelProperty
註解的描述中他,添加相應的校驗說明,以便於使用方查看。
當請求參數校驗出現錯誤信息的時候,錯誤格式能夠修改嗎?
答案是確定的。這裏的錯誤信息實際上由Spring Boot的異常處理機制統一組織並返回的,咱們將在後面的教程中詳細介紹,Spring Boot是如何統一處理異常返回以及咱們該如何定時異常返回。
spring-boot-starter-validation
是必須的嗎?
有讀者以前問過,看到不少教程都寫了還要引入spring-boot-starter-validation
依賴,這個依賴究竟是否須要?(本篇中並無引入)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>複製代碼
其實,只須要仔細看一下spring-boot-starter-validation
依賴主要是爲了引入了什麼,再根據當前本身使用的Spring Boot版原本判斷便可。實際上,spring-boot-starter-validation
依賴主要是爲了引入下面這個依賴:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.14.Final</version>
<scope>compile</scope>
</dependency>複製代碼
咱們能夠看看當前工程的依賴中是否有它,就能夠判斷是否還須要額外引入。在Spring Boot 2.1版本中,該依然其實已經包含在了spring-boot-starter-web
依賴中,並不須要額外引入,因此您在本文中找不到這一步。
本文的完整工程能夠查看下面倉庫中的chapter2-3
目錄:
若是您以爲本文不錯,歡迎Star支持,您的關注是我堅持的動力!
本文首發於:blog.didispace.com/spring-boot…
歡迎關注個人公衆號:程序猿DD,得到獨家整理的學習資源和平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客:didispace.com