Spring Boot 2 中的參數校驗 spring-boot-starter-validation/Hibernate Validator

在springboot中常用的用於參數校驗的註解如下:

@AssertFalse 所註解的元素必須是Boolean類型,且值爲false
@AssertTrue 所註解的元素必須是Boolean類型,且值爲true
@DecimalMax 所註解的元素必須是數字,且值小於等於給定的值
@DecimalMin 所註解的元素必須是數字,且值大於等於給定的值
@Digits 所註解的元素必須是數字,且值必須是指定的位數
@Future 所註解的元素必須是將來某個日期
@Max 所註解的元素必須是數字,且值小於等於給定的值
@Min 所註解的元素必須是數字,且值小於等於給定的值
@Range 所註解的元素需在指定範圍區間內
@NotNull 所註解的元素值不能爲null
@NotBlank 所註解的元素值有內容
@Null 所註解的元素值爲null
@Past 所註解的元素必須是某個過去的日期
@PastOrPresent 所註解的元素必須是過去某個或現在日期
@Pattern 所註解的元素必須滿足給定的正則表達式
@Size 所註解的元素必須是String、集合或數組,且長度大小需保證在給定範圍之內
@Email 所註解的元素需滿足Email格式

一、添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

這個starter依賴的是Hibernate Validator。

二、實體類參數校驗

(一)實體類上加上註解

import lombok.Data;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@Data
public class User implements Serializable {

    private String id;

    @NotNull(message = "姓名不能爲空")
    @Size(min = 1, max = 20, message = "姓名長度必須在1-20之間")
    private String name;

    @Min(value = 10, message = "年齡必須大於10")
    @Max(value = 150, message = "年齡必須小於150")
    private Integer age;

    @Email(message = "郵箱格式不正確")
    private String email;
}

(二)Controller中加上註解

在controller中使用@Valid 或者@Validated 註解校驗

import com.chushiyan.validation_tutorial.entity.Result;
import com.chushiyan.validation_tutorial.pojo.User;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestController
@RequestMapping("/user")
public class UserController  {

    @PostMapping
    public Result test(@Valid  @RequestBody User user){
        System.out.println(user);
        return new Result(true,200,"");
    }
}

(三)測試

使用postman發送POST請求:http://localhost:10000/user

{
	"age":120,
	"email":"chushiyan"
}

控制檯打印:

WARN 12476 --- [io-10000-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.chushiyan.validation_tutorial.entity.Result com.chushiyan.validation_tutorial.controller.UserController.test(com.chushiyan.validation_tutorial.pojo.User) with 2 errors: [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [姓名不能爲空]] [Field error in object 'user' on field 'email': rejected value [chushiyan]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@20195c7a,.*]; default message [郵箱格式不正確]] ]

響應的數據:

{
    "timestamp": "2019-12-03T09:14:00.759+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotNull.user.name",
                "NotNull.name",
                "NotNull.java.lang.String",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "姓名不能爲空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "Email.user.email",
                "Email.email",
                "Email.java.lang.String",
                "Email"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.email",
                        "email"
                    ],
                    "arguments": null,
                    "defaultMessage": "email",
                    "code": "email"
                },
                [],
                {
                    "arguments": null,
                    "defaultMessage": ".*",
                    "codes": [
                        ".*"
                    ]
                }
            ],
            "defaultMessage": "郵箱格式不正確",
            "objectName": "user",
            "field": "email",
            "rejectedValue": "chushiyan",
            "bindingFailure": false,
            "code": "Email"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 2",
    "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.chushiyan.validation_tutorial.entity.Result com.chushiyan.validation_tutorial.controller.UserController.test(com.chushiyan.validation_tutorial.pojo.User) with 2 errors: [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [姓名不能爲空]] [Field error in object 'user' on field 'email': rejected value [chushiyan]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@20195c7a,.*]; default message [郵箱格式不正確]] \r\n\tat  (博主進行了省略......)",
    "path": "/user"
}

(四)全局處理異常

上面響應的錯誤肯定是不夠友好的,所以需要進行異常處理。這裏定義一個全局處理函數

import com.chushiyan.validation_tutorial.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 處理所有校驗失敗的異常(MethodArgumentNotValidException異常)
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    // 設置響應狀態碼爲400
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result handleBindGetException(MethodArgumentNotValidException ex) {

        Map<String, Object> body = new LinkedHashMap<String, Object>();
        body.put("timestamp", new Date());

        // 獲取所有異常
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());
        body.put("errors", errors);
        return new Result(false, 20001, "提交的數據校驗失敗", body);
    }
}

(五)再次測試

使用postman發送POST請求:http://localhost:10000/user

{
	"age":120,
	"email":"chushiyan"
}

響應的json數據:

{
    "flag": false,
    "code": 20001,
    "message": "提交的數據校驗失敗",
    "data": {
        "timestamp": "2019-12-03T09:35:02.815+0000",
        "errors": [
            "郵箱格式不正確",
            "姓名不能爲空"
        ]
    }
}

三、單個參數校驗

(一)直接在參數前加上校驗註解:

package com.chushiyan.validation_tutorial.controller;

import com.chushiyan.validation_tutorial.entity.Result;
import com.chushiyan.validation_tutorial.pojo.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestController
@RequestMapping("/user")
@Validated
public class UserController  {

    @GetMapping
    public Result test2(@NotNull(message = "name不能爲空")  String name){
        System.out.println(name);
        return new Result(true,200,"");
    }
}

注意:需要在類上添加@Validated註解,否則不會校驗。

(二)全局處理函數

import com.chushiyan.validation_tutorial.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 處理所有參數校驗時拋出的異常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = ValidationException.class)
    public Result handleBindException(ValidationException ex) {

        Map<String, Object> body = new LinkedHashMap<String, Object>();
        body.put("timestamp", new Date());

        // 獲取所有異常
        List<String> errors = new LinkedList<String>();
        if(ex instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) ex;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                errors.add(item.getMessage());
            }
        }
        body.put("errors", errors);
        return new Result(true, 20001, "提交的參數校驗失敗", body);
    }
}

(二)測試

postman 測試http://localhost:10000/user GET

{
    "flag": true,
    "code": 20001,
    "message": "提交的參數校驗失敗",
    "data": {
        "timestamp": "2019-12-03T09:58:45.212+0000",
        "errors": [
            "name不能爲空"
        ]
    }
}

四、參數校驗分組

在實際開發中經常會遇到這種情況:添加用戶時,id是由後端生成的,不需要校驗id是否爲空,但是修改用戶時就需要校驗id是否爲空。如果在接收參數的User實體類的id屬性上添加NotNull,顯然無法實現。這時候就可以定義分組,在需要校驗id的時候校驗,不需要的時候不校驗。

(一)定義表示組別的接口類

package com.chushiyan.validation_tutorial.validate;
public interface GroupA {
}

(二)在實體類的註解中標記id使用上面定義的組

給id屬性添加分組:

package com.chushiyan.validation_tutorial.pojo;

import com.chushiyan.validation_tutorial.validate.GroupA;
import lombok.Data;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@Data
public class User implements Serializable {

    @NotNull(groups = GroupA.class, message = "id不能爲空")
    private String id;

    @NotNull(message = "姓名不能爲空")
    @Size(min = 1, max = 20, message = "姓名長度必須在1-20之間")
    private String name;

    @Min(value = 10, message = "年齡必須大於10")
    @Max(value = 150, message = "年齡必須小於150")
    private Integer age;

    @Email(message = "郵箱格式不正確")
    private String email;
}

(三)在controller中使用@Validated指定使用哪個組

    @PostMapping
    public Result add(@Validated @RequestBody User user) {
        return new Result(true, 200, "增加用戶成功");
    }

    @PutMapping("/update")
	// 指定GroupA,這樣就會校驗id屬性是否爲空
	// 注意:還得必須添加Default.class,否則不會執行其他的校驗(如我們案例中的@Email)
    public Result update(@Validated({GroupA.class, Default.class}) @RequestBody User user) {
        return new Result(true, 200, "修改用戶成功");
    }

完整代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章