<noframes id="bhrfl"><address id="bhrfl"></address>

    <address id="bhrfl"></address>

    <noframes id="bhrfl"><address id="bhrfl"><th id="bhrfl"></th></address>

    <form id="bhrfl"><th id="bhrfl"><progress id="bhrfl"></progress></th></form>

    <em id="bhrfl"><span id="bhrfl"></span></em>

    全部
    常見問題
    產品動態
    精選推薦

    Spring Boot 2.x:JSR-303實現請求參數校驗

    管理 管理 編輯 刪除

    請求參數的校驗是很多新手開發非常容易犯錯,或存在較多改進點的常見場景。比較常見的問題主要表現在以下幾個方面:

    • 僅依靠前端框架解決參數校驗,缺失服務端的校驗。這種情況常見于需要同時開發前后端的時候,雖然程序的正常使用不會有問題,但是開發者忽略了非正常操作。比如繞過前端程序,直接模擬客戶端請求,這時候就會突然在前端預設的各種限制,直擊各種數據訪問接口,使得我們的系統存在安全隱患。
    • 大量地使用if/else語句嵌套實現,校驗邏輯晦澀難通,不利于長期維護。

    所以,針對上面的問題,建議服務端開發在實現接口的時候,對于請求參數必須要有服務端校驗以保障數據安全與穩定的系統運行。同時,對于參數的校驗實現需要足夠優雅,要滿足邏輯易讀、易維護的基本特點。

    接下來,我們就來詳細說說,如何優雅地實現Spring Boot服務端的請求參數校驗。

    JSR-303

    在開始動手實踐之前,我們先了解一下接下來我們將使用的一項標準規范:JSR-303

    什么是JSR?

    JSR是Java Specification Requests的縮寫,意思是Java 規范提案。是指向JCP(Java Community Process)提出新增一個標準化技術規范的正式請求。任何人都可以提交JSR,以向Java平臺增添新的API和服務。JSR已成為Java界的一個重要標準。

    JSR-303定義的是什么標準?

    JSR-303 是JAVA EE 6 中的一項子規范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的參考實現 . Hibernate Validator 提供了 JSR 303 規范中所有內置 constraint 的實現,除此之外還有一些附加的 constraint。

    Bean Validation中內置的constraint

    387ca202412171721023369.png

    Hibernate Validator附加的constraint

    98c49202412171721198562.png

    在JSR-303的標準之下,我們可以通過上面這些注解,定義各個請求參數的校驗。


    動手實踐

    已經了解了JSR-303之后,接下來我們就來嘗試一下,基于此規范如何實現參數的校驗!

    準備工作

    可以拿任何一個使用Spring Boot 2.x構建的提供RESTful API的項目作為基礎。

    當然,您也可以根據前文再構建一個作為復習,也是完全沒有問題的。

    快速入門

    我們先來做一個簡單的例子,比如:定義字段不能為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;
    
    }
    

    發起一個可以出發nameage、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文檔中的體現

    可能有朋友會問了,我的接口中是定了這么多。上一篇教程中,不是還教了如何自動生成文檔么,那么對于參數的校驗邏輯該如何描述呢?

    這里要分兩種情況,Swagger自身對JSR-303有一定的支持,但是支持的并那么完善,并沒有覆蓋所有的注解的。

    比如,上面我們使用的注解是可以自動生成的,啟動上面我們的實驗工程,然后訪問http://localhost:8080/swagger-ui.html,在Models不是,我們可以看到如下圖所示的內容:

    9139f202412171724292718.png

    其中:nameage字段相比上一篇教程中的文檔描述,多了一些關于校驗相關的說明;而email字段則沒有體現相關校驗說明。目前,Swagger共支持以下幾個注解:@NotNull、@Max、@Min、@Size、@Pattern。在實際開發過程中,我們需要分情況來處理,對于Swagger支自動生成的可以利用原生支持來產生,如果有部分字段無法產生,則可以在@ApiModelProperty注解的描述中他,添加相應的校驗說明,以便于使用方查看。

    番外:也許你會有這些疑問

    當請求參數校驗出現錯誤信息的時候,錯誤格式可以修改嗎?

    答案是肯定的。這里的錯誤信息實際上由Spring Boot的異常處理機制統一組織并返回的,我們將在后面的教程中詳細介紹,Spring Boot是如何統一處理異常返回以及我們該如何定時異常返回。

    spring-boot-starter-validation是必須的嗎?

    有讀者之前問過,看到很多教程都寫了還要引入spring-boot-starter-validation依賴,這個依賴到底是否需要?(本篇中并沒有引入)

    
        org.springframework.boot
        spring-boot-starter-validation
    
    

    其實,只需要仔細看一下spring-boot-starter-validation依賴主要是為了引入了什么,再根據當前自己使用的Spring Boot版本來判斷即可。實際上,spring-boot-starter-validation依賴主要是為了引入下面這個依賴:

    
       org.hibernate.validator
        hibernate-validator
        6.0.14.Final
        compile
    
    

    我們可以看看當前工程的依賴中是否有它,就可以判斷是否還需要額外引入。在Spring Boot 2.1版本中,該依然其實已經包含在了spring-boot-starter-web依賴中,并不需要額外引入,所以您在本文中找不到這一步。


    注:本文轉載自“程序猿DD”,如有侵權,請聯系刪除!

    請登錄后查看

    哈哈哈醬 最后編輯于2024-12-18 14:55:32

    快捷回復
    回復
    回復
    回復({{post_count}}) {{!is_user ? '我的回復' :'全部回復'}}
    排序 默認正序 回復倒序 點贊倒序

    {{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level }}

    作者 管理員 企業

    {{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推薦': '推薦'}}
    {{item.is_suggest == 1? '取消推薦': '推薦'}}
    沙發 板凳 地板 {{item.floor}}#
    {{item.user_info.title || '暫無簡介'}}
    附件

    {{itemf.name}}

    {{item.created_at}}  {{item.ip_address}}
    打賞
    已打賞¥{{item.reward_price}}
    {{item.like_count}}
    {{item.showReply ? '取消回復' : '回復'}}
    刪除
    回復
    回復

    {{itemc.user_info.nickname}}

    {{itemc.user_name}}

    回復 {{itemc.comment_user_info.nickname}}

    附件

    {{itemf.name}}

    {{itemc.created_at}}
    打賞
    已打賞¥{{itemc.reward_price}}
    {{itemc.like_count}}
    {{itemc.showReply ? '取消回復' : '回復'}}
    刪除
    回復
    回復
    查看更多
    打賞
    已打賞¥{{reward_price}}
    1163
    {{like_count}}
    {{collect_count}}
    添加回復 ({{post_count}})

    相關推薦

    快速安全登錄

    使用微信掃碼登錄
    {{item.label}} 加精
    {{item.label}} {{item.label}} 板塊推薦 常見問題 產品動態 精選推薦 首頁頭條 首頁動態 首頁推薦
    取 消 確 定
    回復
    回復
    問題:
    問題自動獲取的帖子內容,不準確時需要手動修改. [獲取答案]
    答案:
    提交
    bug 需求 取 消 確 定
    打賞金額
    當前余額:¥{{rewardUserInfo.reward_price}}
    {{item.price}}元
    請輸入 0.1-{{reward_max_price}} 范圍內的數值
    打賞成功
    ¥{{price}}
    完成 確認打賞

    微信登錄/注冊

    切換手機號登錄

    {{ bind_phone ? '綁定手機' : '手機登錄'}}

    {{codeText}}
    切換微信登錄/注冊
    暫不綁定
    亚洲欧美字幕
    CRMEB客服

    CRMEB咨詢熱線 咨詢熱線

    400-8888-794

    微信掃碼咨詢

    CRMEB開源商城下載 源碼下載 CRMEB幫助文檔 幫助文檔
    返回頂部 返回頂部
    CRMEB客服