springboot使用hibernate validation對(duì)參數(shù)校驗(yàn)的實(shí)現(xiàn)方法
springboot天生支持使用hibernate validation對(duì)參數(shù)的優(yōu)雅校驗(yàn),如果不使用它,只能對(duì)參數(shù)挨個(gè)進(jìn)行如下方式的手工校驗(yàn),不僅難看,使用起來(lái)還很不方便:
if(StringUtils.isEmpty(userName)){
throw new RuntimeException("用戶(hù)名不能為空");
}
下面將介紹hibernate validation的基本使用方法。
一、引入依賴(lài)
這里在springboot 2.4.1中進(jìn)行實(shí)驗(yàn),引入以下依賴(lài):
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.6.Final</version> </dependency> </dependencies>
二、基本請(qǐng)求參數(shù)校驗(yàn)
如下的一個(gè)spring mvc的請(qǐng)求調(diào)用中有一個(gè)id參數(shù)(Integer類(lèi)型),如果不允許它為空,該怎么做
- 在Controller上加上
@Validated注解 - 在需要校驗(yàn)的字段前面加上
@NotNull(message = "用戶(hù)id不能為空")注解 - 定義全局異常處理類(lèi),定制化返回結(jié)果
@RestControllerAdvice
@Slf4j
public class ValidationAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public WrapperResult handler(Exception e) {
//獲取異常信息,獲取異常堆棧的完整異常信息
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
//日志輸出異常詳情
log.error(sw.toString());
return WrapperResult.faild("服務(wù)異常,請(qǐng)稍后再試");
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public WrapperResult handler(ConstraintViolationException e) {
StringBuffer errorMsg = new StringBuffer();
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
violations.forEach(x -> errorMsg.append(x.getMessage()).append(";"));
return WrapperResult.faild(errorMsg.toString());
}
}
Controller層代碼如下所示:
@RestController
@Slf4j
@RequestMapping("/user")
@Validated
public class UserController {
/**
* 根據(jù)id查詢(xún)用戶(hù)信息
*
* @param id
* @return
*/
@GetMapping
public WrapperResult<UserModel> findUser(@NotNull(message = "用戶(hù)id不能為空")
@RequestParam(value = "id")
String id) {
return WrapperResult.success(new UserModel());
}
}
如果發(fā)起請(qǐng)求127.0.0.1:8080/user?id= 則會(huì)返回結(jié)果
{
"status": 1,
"data": "用戶(hù)id不能為空;",
"msg": "FAIL",
"success": false
}
三、對(duì)象內(nèi)參數(shù)校驗(yàn)
上面是GET請(qǐng)求,下面介紹POST請(qǐng)求,請(qǐng)求對(duì)象內(nèi)的參數(shù)校驗(yàn)。
1.Controller類(lèi)上加上@Validated注解
@RestController
@Slf4j
@RequestMapping("/user")
**@Validated**
public class UserController {
}
2.在POST請(qǐng)求方法參數(shù)前面加上@Validated 注解
@PostMapping("/mobile-regist")
public WrapperResult<Boolean> mobileRegit(@Validated @RequestBody UserModel userModel) {
return WrapperResult.success(true);
}
3.在上面介紹的ValidationAdvice類(lèi)中加上對(duì)象參數(shù)校驗(yàn)異常捕獲
//處理校驗(yàn)異常,對(duì)于對(duì)象類(lèi)型的數(shù)據(jù)的校驗(yàn)異常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public WrapperResult handler(MethodArgumentNotValidException e) {
StringBuffer sb = new StringBuffer();
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
allErrors.forEach(msg -> sb.append(msg.getDefaultMessage()).append(";"));
return WrapperResult.faild(sb.toString());
}
UserModel類(lèi)的定義如下:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {
@NotEmpty(message = "姓名不能為空")
private String name;
@NotEmpty(message = "手機(jī)號(hào)不能為空")
// @Mobile(message = "手機(jī)號(hào)格式不正確")
private String mobile;
@NotEmpty(message = "電子郵箱不能為空")
@Email(message = "電子郵箱格式不正確")
private String email;
private String password;
private String address;
@NotNull(message = "年齡不能為空")
@Min(value = 12, message = "允許注冊(cè)年齡最小為12歲")
@Max(value = 24, message = "允許年齡最大為24歲")
private Integer age;
@NotEmpty(message = "聯(lián)系人不允許為空")
@Size(min = 1, max = 3, message = "聯(lián)系人長(zhǎng)度只允許1到3之間")
private List<String> contacts;
}
如果POST請(qǐng)求如下所示
{
"name":"",
"mobile":"12666666666",
"email":"",
"password":"",
"address":"",
"age": null,
"contacts":[
]
}
則會(huì)返回如下定制化返回結(jié)果:
{
"status": 1,
"data": "電子郵箱不能為空;聯(lián)系人長(zhǎng)度只允許1到3之間;年齡不能為空;聯(lián)系人不允許為空;姓名不能為空;手機(jī)號(hào)格式不正確;",
"msg": "FAIL",
"success": false
}
四、自定義校驗(yàn)器
像是@NotNull、@Email等注解都是hibernate validation 內(nèi)置的注解,我們想開(kāi)發(fā)像是@Email注解一樣功能的注解,如何做呢,比如@Mobile,它的使用方法將和@Email一模一樣。
首先,先定義一個(gè)工具類(lèi)存放ValidationUtil兩個(gè)常量值
public class ValidationUtil {
//手機(jī)號(hào)校驗(yàn)正則
public static final String MOBILE_REGX = "^[1][3-9][0-9]{9}$";
public static final String MOBILE_MSG = "手機(jī)號(hào)格式錯(cuò)誤";
}
1.定義注解Mobile
具體代碼可以參考@Email的實(shí)現(xiàn),直接將Email名字改成Mobile即可,如下所示:
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Mobile {
String message() default ValidationUtil.MOBILE_MSG;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default ValidationUtil.MOBILE_REGX;
Pattern.Flag[] flags() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
Mobile[] value();
}
}
2.定義MobileValidator實(shí)現(xiàn)對(duì)參數(shù)的校驗(yàn)邏輯
public class MobileValidator implements ConstraintValidator<Mobile, String> {
private String regexp;
@Override
public void initialize(Mobile constraintAnnotation) {
//獲取校驗(yàn)的手機(jī)號(hào)的格式
this.regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.hasText(value)) {
return true;
}
return value.matches(regexp);
}
}
3.使用方法和@Email一模一樣
不贅述
五、分組校驗(yàn)
假設(shè)一個(gè)用戶(hù)注冊(cè)的場(chǎng)景,用戶(hù)注冊(cè)有三種方式
- 用戶(hù)名+圖形驗(yàn)證碼注冊(cè)
- 郵箱+郵箱驗(yàn)證碼注冊(cè)
- 手機(jī)號(hào)+短信驗(yàn)證碼注冊(cè)
用戶(hù)注冊(cè)的時(shí)候除了方式不一樣,其他用戶(hù)信息基本相同,后端開(kāi)了三個(gè)接口對(duì)應(yīng)著著三種注冊(cè)方式,請(qǐng)求體中我們使用一個(gè)Model封裝了以上所有信息,包含著用戶(hù)名、郵箱、手機(jī)號(hào)等信息,這時(shí)候不同的接口被調(diào)用,model中需要校驗(yàn)的參數(shù)就不一樣了:
用戶(hù)名注冊(cè)的時(shí)候郵箱地址和手機(jī)號(hào)可以為空,但是用戶(hù)名不能為空;通過(guò)郵箱注冊(cè)的時(shí)候,郵箱地址不能為空,但是用戶(hù)名和手機(jī)號(hào)可以為空;......
分組校驗(yàn)專(zhuān)門(mén)應(yīng)對(duì)這種情況。
1.首先定義三個(gè)接口,表示三種組類(lèi)別
public interface ValidEmail {
}
public interface ValidMobile {
}
public interface ValidUserName {
}
2.在UserModel實(shí)體類(lèi)上指名組類(lèi)別
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class UserModel {
@NotEmpty(message = "姓名不能為空", groups = {ValidUserName.class})
@UserName(groups = {ValidUserName.class})
private String name;
@NotEmpty(message = "手機(jī)號(hào)不能為空", groups = {ValidMobile.class})
@Mobile(groups = {ValidMobile.class})
private String mobile;
@NotEmpty(message = "電子郵箱不能為空", groups = {ValidEmail.class})
@Email(message = "電子郵箱格式不正確", groups = {ValidEmail.class})
private String email;
private String password;
private String address;
@NotNull(message = "年齡不能為空")
@Min(value = 12, message = "允許注冊(cè)年齡最小為12歲", groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
@Max(value = 24, message = "允許年齡最大為24歲",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
private Integer age;
@NotEmpty(message = "聯(lián)系人不允許為空",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
@Size(min = 1, max = 3, message = "聯(lián)系人長(zhǎng)度只允許1到3之間",groups = {ValidEmail.class,ValidMobile.class,ValidUserName.class})
private List<String> contacts;
}
3.Controller方法上指名驗(yàn)證組別
/**
* 手機(jī)號(hào)注冊(cè)
*
* @param userModel
* @return
*/
@PostMapping("/mobile-regist")
public WrapperResult<Boolean> mobileRegit(@Validated(ValidMobile.class) @RequestBody UserModel userModel) {
return WrapperResult.success(true);
}
這時(shí)候進(jìn)行如下請(qǐng)求:
POST http://127.0.0.1:8080/user/mobile-regist
{
"mobile":"12666666666",
"password":"",
"address":"",
"age": null,
"contacts":[
]
}
則會(huì)返回結(jié)果:
{
"status": 1,
"data": "聯(lián)系人長(zhǎng)度只允許1到3之間;手機(jī)號(hào)格式錯(cuò)誤;聯(lián)系人不允許為空;",
"msg": "FAIL",
"success": false
}
該請(qǐng)求中并沒(méi)有傳遞email和username字段,而且結(jié)果中也未校驗(yàn)出這兩個(gè)字段,符合預(yù)期結(jié)果。
六、手動(dòng)校驗(yàn)
此處的手動(dòng)校驗(yàn)并非是使用if/else進(jìn)行簡(jiǎn)單的手動(dòng)校驗(yàn),而是使用Validation自帶的校驗(yàn)工具對(duì)使用了@NotNull等注解的實(shí)體對(duì)象進(jìn)行屬性校驗(yàn)。
首先先獲取Valiation對(duì)象:
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
1. 全屬性校驗(yàn)
/**
* 驗(yàn)證某個(gè)對(duì)象所有字段
*
* @param obj
* @param <T>
* @return
*/
public static <T> ValidationResult validateEntity(T obj) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validate(obj, Default.class);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(cv.getPropertyPath().toString(), cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
2.某個(gè)字段的單獨(dú)校驗(yàn)
/**
* 驗(yàn)證某個(gè)對(duì)象某個(gè)字段
*
* @param obj
* @param propertyName
* @param <T>
* @return
*/
public static <T> ValidationResult validateProperty(T obj, String propertyName) {
ValidationResult result = new ValidationResult();
Set<ConstraintViolation<T>> set = validator.validateProperty(obj, propertyName, Default.class);
if (!CollectionUtils.isEmpty(set)) {
result.setHasErrors(true);
Map<String, String> errorMsg = new HashMap<>();
for (ConstraintViolation<T> cv : set) {
errorMsg.put(propertyName, cv.getMessage());
}
result.setErrorMsg(errorMsg);
}
return result;
}
ValidationResult的定義如下:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class ValidationResult {
private Boolean hasErrors;
private Map<String, String> errorMsg;
}
七、文件上傳校驗(yàn)
1.tomcat容器下文件上傳校驗(yàn)
在springboot+tomcat架構(gòu)下的文件上傳校驗(yàn),假如已經(jīng)有了如下的配置:
spring: servlet: multipart: max-file-size: 1MB max-request-size: 1MB
這表示只允許上傳小于1MB大小的文件,如果不指定異常處理器,默認(rèn)會(huì)報(bào)前端400,在ValidationAdvice類(lèi)中添加如下代碼可以自定義返回結(jié)果:
//文件上傳文件大小超出限制
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseBody
public WrapperResult<Map<String,Object>> fileSizeException(MaxUploadSizeExceededException exception) {
log.error("文件太大,上傳失敗",exception);
return WrapperResult.faild("只允許上傳不大于"+exception.getMaxUploadSize()+"的文件");
}
2.其它容器
在Jetty容器中1中的方法可能會(huì)失效,未驗(yàn)證;在undertow容器中是一定會(huì)失效,已經(jīng)驗(yàn)證。undertow容器畢竟和spring-boot沒(méi)有完全打磨好,不建議現(xiàn)階段使用。
八、附錄
1.所有校驗(yàn)規(guī)則注解說(shuō)明
| 注解 | 說(shuō)明 |
|---|---|
| @Null | 被注解的元素必須為空 |
| @NotNull | 被注解的元素必須不為空 |
| @AssertTrue | 被注解的元素必須為true |
| @AssertFlase | 被注解的元素必須為false |
| @Min(value) | 被注解的元素必須是數(shù)字,且必須大于指定的最小值 |
| @Max(value) | 被注解的元素必須是數(shù)字,且必須小于指定的最大值 |
| @DecimalMin(value) | 被注解的元素必須是數(shù)字,且必須大于指定的最小值 |
| @DecaimalMax(value) | 被注解的元素必須是數(shù)字,且必須小于指定的最大值 |
| @Size(max=,min=) | 被注解元素的大小必須在指定的范圍內(nèi) |
| @Digit(integer,fraction) | 被注解元素必須是數(shù)字,且其值必須在可接受的范圍內(nèi) |
| @Past | 被注解元素必須是一個(gè)過(guò)去的日期 |
| @Futrue | 被注解元素必須是一個(gè)將來(lái)的日期 |
| @Pattern(regex=,flag=) | 被注解元素必須符合指定的正則表達(dá)式 |
| @NotBlank | 驗(yàn)證非空,且長(zhǎng)度必須大于0 |
| 被注解的元素必須是電子郵件地址 | |
| @Length(max=,min=) | 被注解的字符串大小必須在指定的范圍內(nèi) |
| @NotEmpty | 被注解的字符串必須非空 |
| @Range(max=,min=) | 被注解的元素必須在指定范圍內(nèi) |
2.校驗(yàn)規(guī)則注解例子
// 空和非空檢查: @Null、@NotNull、@NotBlank、@NotEmpty
@Null(message = "驗(yàn)證是否為 null")
private Integer isNull;
@NotNull(message = "驗(yàn)證是否不為 null, 但無(wú)法查檢長(zhǎng)度為0的空字符串")
private Integer id;
@NotBlank(message = "檢查字符串是不是為 null,以及去除空格后長(zhǎng)度是否大于0")
private String name;
@NotEmpty(message = "檢查是否為 NULL 或者是 EMPTY")
private List<String> stringList;
// Boolean值檢查: @AssertTrue、@AssertFalse
@AssertTrue(message = " 驗(yàn)證 Boolean參數(shù)是否為 true")
private Boolean isTrue;
@AssertFalse(message = "驗(yàn)證 Boolean 參數(shù)是否為 false ")
private Boolean isFalse;
// 長(zhǎng)度檢查: @Size、@Length
@Size(min = 1, max = 2, message = "驗(yàn)證(Array,Collection,Map,String)長(zhǎng)度是否在給定范圍內(nèi)")
private List<Integer> integerList;
@Length(min = 8, max = 30, message = "驗(yàn)證字符串長(zhǎng)度是否在給定范圍內(nèi)")
private String address;
// 日期檢查: @Future、@FutureOrPresent、@Past、@PastOrPresent
@Future(message = "驗(yàn)證日期是否在當(dāng)前時(shí)間之后")
private Date futureDate;
@FutureOrPresent(message = "驗(yàn)證日期是否為當(dāng)前時(shí)間或之后")
private Date futureOrPresentDate;
@Past(message = "驗(yàn)證日期是否在當(dāng)前時(shí)間之前")
private Date pastDate;
@PastOrPresent(message = "驗(yàn)證日期是否為當(dāng)前時(shí)間或之前")
private Date pastOrPresentDate;
// 其它檢查: @Email、@CreditCardNumber、@URL、@Pattern、
@ScriptAssert、@UniqueElements
@Email(message = "校驗(yàn)是否為正確的郵箱格式")
private String email;
@CreditCardNumber(message = "校驗(yàn)是否為正確的信用卡號(hào)")
private String creditCardNumber;
@URL(protocol = "http", host = "127.0.0.1", port = 8080, message= "校驗(yàn)是否為正確的URL地址")
private String url;
@Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "正則校驗(yàn)是否為正確的手機(jī)號(hào)")
private String phone;
// 對(duì)關(guān)聯(lián)對(duì)象元素進(jìn)行遞歸校驗(yàn)檢查
@Valid
@UniqueElements(message = "校驗(yàn)集合中的元素是否唯一")
private List<CalendarEvent> calendarEvent;
@Data
@ScriptAssert(lang = "javascript", script ="_this.startDate.before(_this.endDate)",message = "通過(guò)腳本表達(dá)式校驗(yàn)參數(shù)")
private class CalendarEvent {
private Date startDate;
private Date endDate;
}
// 數(shù)值檢查: @Min、@Max、@Range、@DecimalMin、@DecimalMax、@Digits
@Min(value = 0, message = "驗(yàn)證數(shù)值是否大于等于指定值")
@Max(value = 100, message = "驗(yàn)證數(shù)值是否小于等于指定值")
@Range(min = 0, max = 100, message = "驗(yàn)證數(shù)值是否在指定值區(qū)間范圍內(nèi)")
private Integer score;
@DecimalMin(value = "10.01", inclusive = false, message = "驗(yàn)證數(shù)值是否大于等于指定值")
@DecimalMax(value = "199.99", message = "驗(yàn)證數(shù)值是否小于等于指定值")
@Digits(integer = 3, fraction = 2, message = "限制整數(shù)位最多為3,小數(shù)位最多為2")
private BigDecimal money;
九、源代碼地址
https://gitee.com/kdyzm/validation-spring-boot-demo
到此這篇關(guān)于spring-boot 使用hibernate validation對(duì)參數(shù)進(jìn)行優(yōu)雅的校驗(yàn)的文章就介紹到這了,更多相關(guān)spring-boot校驗(yàn)參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot?表單提交全局日期格式轉(zhuǎn)換器實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot?表單提交全局日期格式轉(zhuǎn)換器,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
配置JAVA環(huán)境變量中CLASSPATH變量的作用
這篇文章主要介紹了配置JAVA環(huán)境變量中CLASSPATH變量的作用,需要的朋友可以參考下2023-06-06
SpringBoot 并發(fā)登錄人數(shù)控制的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot 并發(fā)登錄人數(shù)控制的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
Java之MyBatis的Dao方式以及Dao動(dòng)態(tài)代理詳解
這篇文章主要介紹了Java之MyBatis的Dao方式以及Dao動(dòng)態(tài)代理詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
IDEA入門(mén)級(jí)使用教程你居然還在用eclipse?
上個(gè)月,idea的使用量超越eclipse的消息席卷了整個(gè)IT界,idea到底好在哪里呢?下面小編通過(guò)本文給大家詳細(xì)介紹下IDEA入門(mén)級(jí)使用教程,非常詳細(xì),感興趣的朋友一起看看吧2020-10-10
Hadoop環(huán)境配置之hive環(huán)境配置詳解
這篇文章主要介紹了Hadoop環(huán)境配置之hive環(huán)境配置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
SpringCloud使用AOP統(tǒng)一處理Web請(qǐng)求日志實(shí)現(xiàn)步驟
這篇文章主要為大家介紹了SpringCloud使用AOP統(tǒng)一處理Web請(qǐng)求日志實(shí)現(xiàn)步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
Java中new Date().getTime()指定時(shí)區(qū)的時(shí)間戳問(wèn)題小結(jié)
本文主要介紹了Java中new Date().getTime()時(shí)間戳問(wèn)題小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07

