springMvc中數據校驗

springMvc中數據校驗

一、簡介

JSR(java specification requests)是java規範提案,其中JSR-303提供了java中bean數據檢測的標準。這裏介紹在spring web開發中,對參數請求進行校驗。

二、數據校驗

JSR(java specification requests)是java規範提案,其中JSR-303提供了java中bean數據檢測的標準。使用註解對數據進行校驗。

2.1 JSR-303常用校驗註解

2.1.1 JSR-303常用校驗註解
註解 功能
@DecimalMax(value) 參數爲數字且小於或等於(可指定是否包含)指定的最大值
@DecimalMin(value) 參數爲數字且大於或等於(可指定是否包含)指定的最小值
@Digits(integer,fraction) 參數爲數字,可指定整數和小數部分位數
@Max(value) 參數爲數字且小於或等於(可指定是否包含)指定的最大值
@Min(value) 參數爲數字且大於或等於(可指定是否包含)指定的最小值
@Positive 參數爲正數
@PositiveOrZero 參數爲正數或等於0
@Negative 參數爲負數
@NegativeOrZero 參數爲負數或等於0
@Future 參數爲時間類型且大於當前時間
@FutureOrPresent 參數爲時間類型且大於或等於當前時間
@Past 參數爲時間類型且小於當前時間
@PastOrPresent 參數爲時間類型且小於或等於當前時間
@Email 參數爲郵箱,可指定匹配的正則表達式
@NotBlank 參數爲字符串,不能爲空且不能全是空白符
@NotEmpty 參數不能爲空,可用於字符串、集合、hash、數組
@NotNull 參數不能爲null
@Null 參數必須爲null
@Pattern 參數必須匹配指定正則
@Size(min, max) 參數內的元素個數必須在指定範圍內,可用於字符串、集合、hash、數組
@AssertFalse 參數必須爲false
@AssertTrue 參數必須爲true
2.1.2 Hibernate Validator擴展校驗註解

Hibernate Validator是對JSR-303標準的一個實現,可以獨立於hiberate框架而單獨使用。在原標準基礎上,擴展了新的校驗註解,如下:

註解 功能
@Range(min, max) 參數必須在指定範圍內,可用於數字或字符串表示的數值
@Length 參數爲字符串且在在指定範圍內
@URL 參數爲url

2.2 校驗註解源碼解析

這裏以@Min爲例查看源碼:

package javax.validation.constraints;

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.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.Min.List;

//被註解的參數必須大於或等於指定的最小值
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface Min {
    //參數出錯的提示消息
   String message() default "{javax.validation.constraints.Min.message}";

    //分組,可以基於不同的類實例進行不同的參數校驗
   Class<?>[] groups() default { };

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

   //指定的最小值,被校驗參數應該大於或等於
   long value();

   /**
    * Defines several {@link Min} annotations on the same element.
    *
    * @see Min
    */
   @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
   @Retention(RUNTIME)
   @Documented
   @interface List {
      Min[] value();
   }
}

2.4 開啓參數校驗

開啓參數校驗的方式有兩種,@Valid和@Validated,放在需要校驗參數的前面。

2.4.1 @Valid

@Valid是JSR-303規範的校驗機制,不支持分組校驗,不支持在類上校驗,但標識範圍更廣,支持嵌套校驗。源碼如下:

package javax.validation;
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.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
//標識需要校驗的方法、屬性、構造函數、方法參數、使用類型的語句,可用於嵌套校驗
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface Valid {
}
2.4.1 @Validated

@Validated是spring實現校驗機制,是JSR-303的變體,支持分組校驗,但不支持成員屬性校驗,不支持嵌套校驗,源碼如下:

package org.springframework.validation.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//支持類、方法、方法參數校驗
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
   //分組校驗,根據傳入類實例不同而實現不同校驗策略
   Class<?>[] value() default {};
}

2.5 校驗結果獲取

被校驗參數後面添加Errors或BindingResult對象,可以獲取校驗結果。

2.5.1 Errors

Errors是參數校驗結果獲取的原始接口,通過內部方法可獲取各類驗證結果。源碼如下:

package org.springframework.validation;

import java.util.List;

import org.springframework.beans.PropertyAccessor;
import org.springframework.lang.Nullable;

//存儲驗證對象的校驗信息
public interface Errors {

   //返回校驗對象名稱
   String getObjectName();

   //修改內置路徑,默認爲""
   void setNestedPath(String nestedPath);

   //獲取內置路徑
   String getNestedPath();

   //壓入子路徑
   void pushNestedPath(String subPath);

   //彈出子路精
   void popNestedPath() throws IllegalStateException;

   //添加錯誤
   void reject(String errorCode);

   //添加錯誤
   void reject(String errorCode, String defaultMessage);

   //添加錯誤
   void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);

   //添加錯誤
   void rejectValue(@Nullable String field, String errorCode);

   //添加錯誤
   void rejectValue(@Nullable String field, String errorCode, String defaultMessage);

   //添加錯誤
   void rejectValue(@Nullable String field, String errorCode,
         @Nullable Object[] errorArgs, @Nullable String defaultMessage);

   //添加指定Errors的所有錯誤
   void addAllErrors(Errors errors);

   //參數是否有錯
   boolean hasErrors();

   //錯誤個數
   int getErrorCount();

   //返回所有錯誤
   List<ObjectError> getAllErrors();

   //是否有全局錯誤
   boolean hasGlobalErrors();

   //全局錯誤個數
   int getGlobalErrorCount();

   //返回所有全局錯誤
   List<ObjectError> getGlobalErrors();

   //返回第一個全局錯誤
   @Nullable
   ObjectError getGlobalError();

   //是否有字段錯誤
   boolean hasFieldErrors();

   //返回字段錯誤個數
   int getFieldErrorCount();

   //返回所有字段錯誤
   List<FieldError> getFieldErrors();

   //返回第一個字段錯誤
   @Nullable
   FieldError getFieldError();

   //指定字段是否有錯誤
   boolean hasFieldErrors(String field);

   //指定字段錯誤個數
   int getFieldErrorCount(String field);

   //返回指定字段所有錯誤
   List<FieldError> getFieldErrors(String field);

   //返回指定字段第一個錯誤
   @Nullable
   FieldError getFieldError(String field);

   //獲取指定字段的當前值
   @Nullable
   Object getFieldValue(String field);

   //獲取指定字段類型
   @Nullable
   Class<?> getFieldType(String field);
}
2.5.2 BindingResult

BindingResult繼承自Errors,源碼如下:

package org.springframework.validation;

import java.beans.PropertyEditor;
import java.util.Map;

import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.lang.Nullable;


public interface BindingResult extends Errors {
   //獲取目標對象
   @Nullable
   Object getTarget();

   //返回目標對象和驗證結果組成的map
   Map<String, Object> getModel();

   //獲取指定字段的原始值
   @Nullable
   Object getRawFieldValue(String field);

   /**
    * Find a custom property editor for the given type and property.
    * @param field the path of the property (name or nested path), or
    * {@code null} if looking for an editor for all properties of the given type
    * @param valueType the type of the property (can be {@code null} if a property
    * is given but should be specified in any case for consistency checking)
    * @return the registered editor, or {@code null} if none
    */
   @Nullable
   PropertyEditor findEditor(@Nullable String field, @Nullable Class<?> valueType);

   /**
    * Return the underlying PropertyEditorRegistry.
    * @return the PropertyEditorRegistry, or {@code null} if none
    * available for this BindingResult
    */
   @Nullable
   PropertyEditorRegistry getPropertyEditorRegistry();

   /**
    * Resolve the given error code into message codes.
    * <p>Calls the configured {@link MessageCodesResolver} with appropriate parameters.
    * @param errorCode the error code to resolve into message codes
    * @return the resolved message codes
    */
   String[] resolveMessageCodes(String errorCode);

   /**
    * Resolve the given error code into message codes for the given field.
    * <p>Calls the configured {@link MessageCodesResolver} with appropriate parameters.
    * @param errorCode the error code to resolve into message codes
    * @param field the field to resolve message codes for
    * @return the resolved message codes
    */
   String[] resolveMessageCodes(String errorCode, String field);

   //添加錯誤
   void addError(ObjectError error);

   //添加字段值
   default void recordFieldValue(String field, Class<?> type, @Nullable Object value) {
   }

   /**
    * Mark the specified disallowed field as suppressed.
    * <p>The data binder invokes this for each field value that was
    * detected to target a disallowed field.
    * @see DataBinder#setAllowedFields
    */
   default void recordSuppressedField(String field) {
   }

   /**
    * Return the list of fields that were suppressed during the bind process.
    * <p>Can be used to determine whether any field values were targeting
    * disallowed fields.
    * @see DataBinder#setAllowedFields
    */
   default String[] getSuppressedFields() {
      return new String[0];
   }

}

三、數據校驗使用流程

數據校驗的流程如下:

在對象各屬性添加註解校驗邏輯,如註解Min
在controller處理方法的參數上添加校驗註解標識,如註解Validated
在controller處理方法的參數後添加獲取校驗結果參數,如BindingResult
在controller處理方法中處理校驗結果,如bindingResult.getAllErrors方法

四、自定義數據校驗註解

除了使用如@Min、@NotNull等已經存在的註解,還可以自定義校驗註解。步驟如下:

  1. 自定義註解的校驗類,需要實現javax.validation.ConstraintValidator接口;
  2. 自定義校驗註解,需要使用@Constraint標記是校驗註解,同時指定上一步的校驗類;

通過上述步驟後,便生成了自定義的校驗註解,使用同常規校驗註解類似,後文有示例。

五、使用示例

這裏使用spring boot構建項目測試。

4.1 添加maven依賴

添加JSR-303的推薦實現hibernate-validator包的maven依賴, 如下:

<!-- spring-boot-starter-web中包含了hibernate-validator包-->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.1.5.Final</version>
</dependency>

注意代碼中的註釋,當使用spring-boot開發時,spring-boot-starter-web包中已經添加了hibernate-validator依賴,不需要再單獨添加。

4.2 常規校驗示例

4.2.1 校驗對象定義
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.math.BigDecimal;

@Data
public class Apple {
    //同一屬性多註解校驗
    @Pattern(regexp = "\\d{4}", message = "appleId must be 4 number")
    @NotNull(message = "appleId should not  Null")
    private String appleId;
    @Length(min = 2, max = 5, message = "address should be in [2, 5]")
    private String address;
    @DecimalMin(message = "price should gt 0.00", value = "0.00")
    private BigDecimal price;
    @Max(message = "number should less 10", value = 10)
    private int number;
}
4.2.2 請求方法
import com.dragon.study.simplespringstudy.bean.Apple;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;

@RestController
@RequestMapping("check")
public class NormalCheckController {
    //常規校驗
    @RequestMapping("normalCheck")
    public Object normalCheck(
            @Validated Apple apple,
            BindingResult br) {
        if (br.hasErrors()) {
            return br.getAllErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
        }
        return "success";
    }
}
4.2.3 測試

請求:

### 常規校驗
GET http://localhost:9090/check/normalCheck?price=-0.11&number=12&address=a&appleId=01
Accept: application/json

響應:

[
  "price should gt 0.00",
  "number should less 10",
  "address should be in [2, 5]",
  "appleId must be 4 number"
]

4.3 嵌套校驗示例

4.3.1 校驗對象定義
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Data
public class Banana {
    @NotNull(message = "bananaId should not null")
    private String bananaId;
    //嵌套校驗
    @Valid
    @NotNull(message = "farmer should not null")
    private Farmer farmer;

    //內部類
    @Data
    static class Farmer {
        @NotNull(message = "farmerId should not be null")
        private String farmerId;
        @Length(min = 2, max = 5, message = "address should be in [2, 5]")
        private String address;
    }
}
4.3.2 請求方法
import com.dragon.study.simplespringstudy.bean.Banana;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;

@RestController
@RequestMapping("check")
public class NestCheckController {
    //嵌套校驗
    @RequestMapping("nestCheck")
    public Object nestCheck(
            @RequestBody @Validated Banana banana,
            BindingResult br) {
        if (br.hasErrors()) {
            return br.getAllErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
        }
        return "success";
    }
}
4.3.3 測試

請求:

### 嵌套校驗
POST http://localhost:9090/check/nestCheck
Accept: application/json
Content-Type: application/json

{
  "farmer": {
    "address": 1
  }
}

響應:

[
  "address should be in [2, 5]",
  "bananaId should not null",
  "farmerId should not be null"
]

4.4 分組校驗示例

4.4.1 校驗對象定義
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Data
public class Pearl {
    @NotNull(message = "pearlId should not  Null")
    private String pearlId;
    //groups指定分組,不同組使用不同校驗規則
    @Min(value = 1, groups = {Red.class}, message = "red should ge 1")
    @Min(value = 2, groups = {Green.class}, message = "green should ge 2")
    private int weight;

    //用於分組
    public interface Red {
    }
    //用於分組
    public interface Green {
    }
}
4.4.2 請求方法
import com.dragon.study.simplespringstudy.bean.Pearl;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.groups.Default;
import java.util.stream.Collectors;

@RestController
@RequestMapping("check")
public class GroupCheckController {
    //分組校驗
    @RequestMapping("groupCheck")
    public Object groupCheck(
            //指定分組後,只有指定分組的校驗才生效,使用Default.class,則未使用分組的走默認校驗
            @Validated({Pearl.Green.class, Default.class}) Pearl pearl,
            BindingResult br) {
        if (br.hasErrors()) {
            return br.getAllErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
        }
        return "success";
    }
}

特別注意上面的Default.class,表示默認校驗規則也走。

4.4.3 測試

請求:

### 分組校驗
GET http://localhost:9090/check/groupCheck?weight=1
Accept: application/json

響應:

[
  "pearlId should not  Null",
  "green should ge 2"
]

4.5 多對象校驗示例

4.5.1 校驗對象示例
import javax.validation.constraints.NotNull;
public class Peach {
    @NotNull(message = "peachId should not be null")
    private String peachId;
}
import javax.validation.constraints.NotNull;
public class Grape {
    @NotNull(message = "grapeId should not be null")
    private String grapeId;
}
4.5.2 請求方法
import com.dragon.study.simplespringstudy.bean.Grape;
import com.dragon.study.simplespringstudy.bean.Peach;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("check")
public class MultiCheckController {
    //多對象校驗
    @RequestMapping("multiCheck")
    public Object multiCheck(
            @Validated Peach peach,
            BindingResult pbr,
            @Validated Grape grape,
            BindingResult gbr) {

        List<String> errorList = new ArrayList<>();
        if (pbr.hasErrors()) {
            errorList.addAll(pbr.getAllErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList()));
        }
        if (gbr.hasErrors()) {
            errorList.addAll(gbr.getAllErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList()));
        }
        if (CollectionUtils.isNotEmpty(errorList)) {
            return errorList;
        }
        return "success";
    }
}
4.5.3 測試

請求:

### 多對象校驗
GET http://localhost:9090/check/multiCheck
Accept: application/json

響應:

[
  "peachId should not be null",
  "grapeId should not be null"
]

4.6 直接參數校驗示例

spring中直接參數校驗是直接拋異常,在類上添加註解@Validated,然後在請求方法參數上添加校驗規則。示例如下:

4.6.1 請求方法
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotNull;

@RestController
@RequestMapping("check")
@Validated
public class DirectCheckController {
    //直接校驗
    @RequestMapping("directCheck")
    public Object directCheck(
            @NotNull(message = "id should not be null") String id,
            @NotNull(message = "name should not be null") String name
    ) {
        return "success";
    }
}
4.6.2 測試

請求:

### 直接校驗
GET http://localhost:9090/check/directCheck
Accept: application/json

響應:

{
  "timestamp": "2020-06-27T06:29:38.054+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "directCheck.id: id should not be null, directCheck.name: name should not be null",
  "path": "/check/directCheck"
}

4.7 自定義校驗示例

4.7.1 自定義註解校驗器
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

//自定義註解校驗類
public class AuthValidator implements ConstraintValidator<Auth, String> {
    //初始化,可獲取註解參數
    @Override
    public void initialize(Auth constraintAnnotation) {
    }

    //自定義校驗規則,True:通過 False:不通過
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        return !StringUtils.isEmpty(s) && s.contains("123");
    }
}
4.7.2 自定義校驗註解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) //定義註解修飾對象
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AuthValidator.class}) //@Constraint表示是驗證註解,同時指定驗證類
public @interface Auth {
    String message() default "no auth"; //錯誤提示信息

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

    Class<? extends Payload>[] payload() default {};
}
4.7.3 校驗對象定義
import lombok.Data;
@Data
public class Person {
    @Auth(message = "no right")
    private String name;
}
4.7.4 請求方法
import com.dragon.study.simplespringstudy.bean.Person;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;

@RestController
@RequestMapping("check")
public class CustomCheckController {
    //子定義註解校驗
    @RequestMapping("customCheck")
    public Object customCheck(
            @Validated Person person,
            BindingResult br) {
        if (br.hasErrors()) {
            return br.getAllErrors()
                    .stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
        }
        return "success";
    }
}
4.7.5 測試

請求:

### 自定義校驗
GET http://localhost:9090/check/customCheck?name=app
Accept: application/json

響應:

[
  "no right"
]

再次請求:

### 自定義校驗
GET http://localhost:9090/check/customCheck?name=app123
Accept: application/json

再次響應:

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