在Spring中進行參數校驗,格式化,參數範圍,自定義校驗規則的處理


使用背景

目前在項目中,參數校驗的工做都在前端完成,然後端接口只處理業務邏輯,可是這種方式不太合理,繞過頁面直接進行http請求,會有系統異常以及髒數據的風險,因此推薦使用Bean Validation 基於 JSR 303 - Bean Validation參數校驗框架在後端接口作參數校驗,格式化校驗,以及參數可選範圍的校驗,這樣既能規避大部分因參數缺失而產生的系統異常,也能在接口聯調階段,提升聯調效率,減小先後端同窗在聯調時排查問題的時間html

Hibernate Validator 是 Bean Validation 的參考實現。Hibernate Validator 提供了 JSR 303 規範中全部內置 constraint 的實現,目前已升級到Bean Validation 2.0 / JSR - 380,除此以外還有一些附加的 constraint。該Hibernate不是ORM的Hibernate前端

舉例Bean Validation 中的 constraint (約束,限制),Bean Validation 的註解在javax.validation.constraints下java

約束 限制
@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(value) 被註釋的元素必須符合指定的正則表達式

Hibernate Validator 附加的 constraint / Hibernate Validator是JSR - 303 的最好實現,目前規範已升級到 JSRgit

約束 限制
@Email 被註釋的元素必須是電子郵箱地址
@Length 被註釋的字符串的大小必須在指定的範圍內
@NotEmpty 被註釋的字符串的必須非空
@Range 被註釋的元素必須在合適的範圍內

使用方法

Bean Validation 是JDK 1.6 +後內置的,包名爲javax.validation.constraints正則表達式

Hibernate Validator 則須要引入jar包,包名爲org.hibernate.validator.constraintsspring

POM.xmljson

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.1.Final</version> 
</dependency>
複製代碼

實體類後端

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import java.util.Date;

public class ValidationDemo {
    private String id;

    @Length(min = 2, max = 6, message = "用戶名長度要求在{min}-{max}之間")
    @NotNull(message = "用戶名不可爲空")
    private String userName;

    @Email(message = "郵箱格式錯誤")
    private String email;

    @Past(message = "出生日期錯誤")
    private Date birthDay;

    @Min(value = 18, message = "年齡錯誤")
    @Max(value = 80, message = "年齡錯誤")
    private Integer age;

    @Range(min = 0, max = 1, message = "性別選擇錯誤")
    private Integer sex;
}
複製代碼

關於@Valid和Validated的比較,根據實際需求需求選擇bash

@Valid : 沒有分組功能,能夠用在方法、構造函數、方法參數和成員屬性(field)上,若是一個待驗證的pojo類,其中還包含了待驗證的對象,須要在待驗證對象上註解@valid,才能驗證待驗證對象中的成員屬性app

@Validated :提供分組功能,能夠在入參驗證時,根據不一樣的分組採用不一樣的驗證機制,用在類型、方法和方法參數上。但不能用於成員屬性(field)。

Controller

-- @Valid 表示對該實體進行校驗
-- BindingResult 則保存對參數的校驗結果
@RequestMapping(value = "validation", method = RequestMethod.POST)
public JsonResult validation(@Valid @RequestBody ValidationDemo demo, BindingResult result) {
    JsonResult jsonResult = new JsonResult();
    if (result.hasErrors()) {
        result.getAllErrors().forEach(err -> {
            jsonResult.setCode(ApiConstants.JsonResult.FAIL);
            jsonResult.setMsg(err.getDefaultMessage());
        });
    }
    return jsonResult;
}
複製代碼

RequestBody

{
  "age": 19,
  "birthDay": "2019-04-14T09:05:39.604Z",
  "email": "string",
  "id": "string",
  "sex": 0,
  "userName": "string"
}
複製代碼

Response

{
  "code": 1,
  "msg": "郵箱格式錯誤",
  "total": 0,
  "totalpage": 0
}
複製代碼
因而可知,參數的校驗已經生效,由於email不符合@Email的校驗規則,具體校驗規則能夠查看@Email的實現EmailValidator.java

userName 的錯誤message 裏面有{min} - {max} ?

RequestBody

{
  "age": 19,
  "birthDay": "2019-04-14T09:05:39.604Z",
  "email": "string",
  "id": "string",
  "sex": 0,
  "userName": ""
}
複製代碼

Response

{
  "code": 1,
  "msg": "用戶名長度要求在2-6之間",
  "total": 0,
  "totalpage": 0
}
複製代碼

Hibernate Validator 經過EL表達式獲取到了在@length中定義的min以及max屬性的值

在上面的Controller中,須要在在接口參數中,增長一個BindingResult來接收校驗的結果,每個BindingResult與@Valid是一一對應的,若是有多個@Valid,那麼須要對個BindResult來保存校驗結果

進階使用,統一處理校驗結果並返回前端

在 ResponseEntityExceptionHandler (Line 162) 中,若是驗證出現異常的時候是拋出了MethodArgumentNotValidException

MethodArgumentNotValidException 描述:

Exception to be thrown when validation on an argument annotated with {@code @Valid} fails.

當使用@Valid註解的參數驗證失敗是拋出異常

複製代碼

因此在BaseController中對MethodArgumentNotValidException進行處理

Controller

-- 對接口進行簡化,經過異常捕獲的方式對校驗結果返回給前端
@RequestMapping(value = "validation", method = RequestMethod.POST)
public JsonResult validation(@Valid @RequestBody ValidationDemo demo) {
    return null;
}
複製代碼

BaseController

if (e instanceof MethodArgumentNotValidException) {
    res.setCode(ApiConstants.JsonResult.FAIL);
    res.setMsg(JSONArray.toJSONString(((MethodArgumentNotValidException) e).getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList())));
}
複製代碼

Response

{
  "code": 1,
  "msg": "[\"年齡錯誤\",\"郵箱格式錯誤\"]",
  "total": 0,
  "totalpage": 0
}
複製代碼

分組校驗

在實際使用中,有可能咱們針對一個屬性,有多個校驗規則,這時候就要使用到分組校驗了

改造實體

public class ValidationDemo {
    private String id;

    @Length(min = 2, max = 6, message = "用戶名長度要求在{min}-{max}之間")
    @NotNull(message = "用戶名不可爲空")
    private String userName;

    // 表示分組爲Adult時使用該校驗規則
    @Email(message = "郵箱格式錯誤")
    @NotBlank(message = "郵箱不可爲空", groups = {ValidationDemo.Adult.class})
    private String email;

    @Past(message = "出生日期錯誤")
    private Date birthDay;

    @Min(value = 18, message = "年齡錯誤")
    @Max(value = 80, message = "年齡錯誤")
    private Integer age;

    @Range(min = 0, max = 1, message = "性別選擇錯誤")
    private Integer sex;

    // 添加兩個分組
    public interface Adult {
    }

    public interface Minor {
    }
}
複製代碼

測試一下

// 這裏將分組設置爲Minor,目的是不校驗郵箱字段
@RequestMapping(value = "validation", method = RequestMethod.POST)
public JsonResult validation(@Validated({ValidationDemo.Adult.class}) @RequestBody ValidationDemo demo) {
    return null;
}

RequestBody:
{
  "age": 0,
  "birthDay": "2019-04-14T10:39:08.501Z",
  "email": "",
  "id": "string",
  "sex": 0,
  "userName": "string"
}
Response:
{
  "code": 1,
  "msg": "[\"郵箱不可爲空\"]",
  "total": 0,
  "totalpage": 0
}
複製代碼

若是是接口使用Minor分組呢?

RequestBody:
{
  "age": 0,
  "birthDay": "2019-04-14T10:39:08.501Z",
  "email": "",
  "id": "string",
  "sex": 0,
  "userName": "string"
}
Response:
{
  "code": 0,
  "data": [
    {}
  ],
  "extra": "string",
  "msg": "string",
  "result": {},
  "total": 0,
  "totalpage": 0
}
複製代碼
並無提示郵箱不可爲空,因而可知,分組驗證已經生效

自定義校驗規則

例如新建一個自定義日期格式的校驗

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Constraint(validatedBy = {DateFormatByPatternValidator.class})
public @interface DateFormatByPattern {
    String pattern() default "yyyy-MM-dd HH:mm";

    //默認錯誤消息
    String message() default "日期格式錯誤";

    //分組
    Class<?>[] groups() default {};

    //負載
    Class<? extends Payload>[] payload() default {};
}

複製代碼

同時新建一個對應的校驗器

public class DateFormatByPatternValidator implements ConstraintValidator<DateFormatByPattern, String> {

    private DateFormatByPattern dateFormatByPattern;

    @Override
    public void initialize(DateFormatByPattern constraintAnnotation) {
        dateFormatByPattern = constraintAnnotation;
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        //假如參數爲空的話,返回true,若是要對參數值進行非空校驗的話,經過@NotNull來校驗,這樣與日期格式校驗解耦
        if (StringUtils.isNotBlank(value)) {
            String pattern = dateFormatByPattern.pattern();
            SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
            try {
                dateFormat.parse(value);
            } catch (ParseException e) {
                return false;
            }
        }
        return true;
    }
}
複製代碼

改造實體

//使用自定義規則校驗前端參數
@DateFormatByPattern(pattern = "yyyy-MM-dd")
//由於同時用到了分組校驗,因此在stringDate上添加@Valid,使校驗生效
@Valid
private String stringDate;
複製代碼

測試一下

RequestBody:
{
  "age": 0,
  "birthDay": "2019-04-15T08:23:21.683Z",
  "email": "",
  "id": "string",
  "sex": 0,
  "stringDate": "string",
  "userName": "string"
}
Response:
{
  "code": 1,
  "msg": "[\"日期格式錯誤\",\"郵箱不可爲空\",\"年齡錯誤\"]",
  "total": 0,
  "totalpage": 0
}
複製代碼
因而可知,自定義校驗已生效

參考文檔

Hibernate Validation自定義註解校驗

自定義校驗器註解

SpringMVC集成Bean Validation 1.0(JSR-303)

經過Hibernate-Validation進行參數驗證

相關文章
相關標籤/搜索