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 | 參數爲時間類型且小於或等於當前時間 |
參數爲郵箱,可指定匹配的正則表達式 | |
@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、@NotNull等已經存在的註解,還可以自定義校驗註解。步驟如下:
- 自定義註解的校驗類,需要實現javax.validation.ConstraintValidator接口;
- 自定義校驗註解,需要使用@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