淺談自定義校驗注解ConstraintValidator
一、前言
系統(tǒng)執(zhí)行業(yè)務邏輯之前,會對輸入數(shù)據(jù)進行校驗,檢測數(shù)據(jù)是否有效合法的。所以我們可能會寫大量的if else等判斷邏輯,特別是在不同方法出現(xiàn)相同的數(shù)據(jù)時,校驗的邏輯代碼會反復出現(xiàn),導致代碼冗余,閱讀性和可維護性極差。
JSR-303是Java為Bean數(shù)據(jù)合法性校驗提供的標準框架,它定義了一整套校驗注解,可以標注在成員變量,屬性方法等之上。
hibernate-validator就提供了這套標準的實現(xiàn),我們在用Springboot開發(fā)web應用時,會引入spring-boot-starter-web依賴,它默認會引入spring-boot-starter-validation依賴,而spring-boot-starter-validation中就引用了hibernate-validator依賴。

但是,在比較高版本的spring-boot-starter-web中,默認不再引用spring-boot-starter-validation,自然也就不會默認引入到hibernate-validator依賴,需要我們手動添加依賴。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.7.Final</version>
</dependency>
hibernate-validator中有很多非常簡單好用的校驗注解,例如NotNull,@NotEmpty,@Min,@Max,@Email,@PositiveOrZero等等。這些注解能解決我們大部分的數(shù)據(jù)校驗問題。如下所示:
package com.nobody.dto;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class UserDTO {
@NotBlank(message = "姓名不能為空")
private String name;
@Min(value = 18, message = "年齡不能小于18")
private int age;
@NotEmpty(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
}
二、自定義參數(shù)校驗器
但是,hibernate-validator中的這些注解不一定能滿足我們?nèi)康男枨?,我們想校驗的邏輯比這復雜。所以,我們可以自定義自己的參數(shù)校驗器。
首先引入依賴是必不可少的。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.7.Final</version>
</dependency>
最近不是基金很火嗎,一大批的韭菜瘋狂地涌入買基金的浪潮中。我就以用戶開戶為例,首先要校驗此用戶是不是成年人(即不能小于18歲),以及名字是不是以"新韭菜"開頭的,符合條件的才允許開戶。
定義一個注解,用于校驗用戶的姓名是不是以“新韭菜”開頭的。
package com.nobody.annotation;
import com.nobody.validator.IsLeekValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Constraint(validatedBy = IsLeekValidator.class) // 指定我們自定義的校驗類
public @interface IsLeek {
/**
* 是否強制校驗
*
* @return 是否強制校驗的boolean值
*/
boolean required() default true;
/**
* 校驗不通過時的報錯信息
*
* @return 校驗不通過時的報錯信息
*/
String message() default "此用戶不是韭零后,無法開戶!";
/**
* 將validator進行分類,不同的類group中會執(zhí)行不同的validator操作
*
* @return validator的分類類型
*/
Class<?>[] groups() default {};
/**
* 主要是針對bean,很少使用
*
* @return 負載
*/
Class<? extends Payload>[] payload() default {};
}
定義校驗類,實現(xiàn)ConstraintValidator接口,接口使用了泛型,需要指定兩個參數(shù),第一個是自定義注解,第二個是需要校驗的數(shù)據(jù)類型。重寫2個方法,initialize方法主要做一些初始化操作,它的參數(shù)是我們使用到的注解,可以獲取到運行時的注解信息。isValid方法就是要實現(xiàn)的校驗邏輯,被注解的對象會傳入此方法中。
package com.nobody.validator;
import com.nobody.annotation.IsLeek;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IsLeekValidator implements ConstraintValidator<IsLeek, String> {
// 是否強制校驗
private boolean required;
@Override
public void initialize(IsLeek constraintAnnotation) {
this.required = constraintAnnotation.required();
}
@Override
public boolean isValid(String name, ConstraintValidatorContext constraintValidatorContext) {
if (required) {
// 名字以"新韭菜"開頭的則校驗通過
return !StringUtils.isEmpty(name) && name.startsWith("新韭菜");
}
return false;
}
}
三、使用自定義注解
通過以上幾個步驟,我們自定義的校驗注解就完成了,我們使用測試下效果。
package com.nobody.dto;
import com.nobody.annotation.IsLeek;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class UserDTO {
@NotBlank(message = "姓名不能為空")
@IsLeek // 我們自定義的注解
private String name;
@Min(value = 18, message = "年齡不能小于18")
private int age;
@NotEmpty(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
}
寫個接口,模擬用戶開戶業(yè)務,調(diào)用測試。注意,記得加上@Valid注解開啟校驗,不然不生效。
package com.nobody.controller;
import com.nobody.dto.UserDTO;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("user")
public class UserController {
@PostMapping("add")
public UserDTO add(@RequestBody @Valid UserDTO userDTO) {
System.out.println(">>> 用戶開戶成功...");
return userDTO;
}
}
如果參數(shù)校驗不通過,會拋出MethodArgumentNotValidException異常,我們?nèi)痔幚硐氯缓蠓祷亟o接口。
package com.nobody.exception;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import lombok.extern.slf4j.Slf4j;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
// 處理接口參數(shù)數(shù)據(jù)格式錯誤異常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public Object errorHandler(HttpServletRequest request, MethodArgumentNotValidException e) {
return e.getBindingResult().getAllErrors();
}
}
我們先測試用戶姓名不帶"新韭菜"前綴的進行測試,發(fā)現(xiàn)校驗不通過,證明注解生效了。
POST http://localhost:8080/user/add
Content-Type: application/json
{"name": "小綠", "age": 19, "email": "845136542@qq.com"}
[
{
"codes": [
"IsLeek.userDTO.name",
"IsLeek.name",
"IsLeek.java.lang.String",
"IsLeek"
],
"arguments": [
{
"codes": [
"userDTO.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
},
true
],
"defaultMessage": "此用戶不是韭零后,無法開戶!",
"objectName": "userDTO",
"field": "name",
"rejectedValue": "小綠",
"bindingFailure": false,
"code": "IsLeek"
}
如果多個參數(shù)校驗失敗,報錯信息也都能獲得。如下所示,姓名和郵箱都校驗失敗。
POST http://localhost:8080/user/add
Content-Type: application/json
{"name": "小綠", "age": 19, "email": "84513654"}
[
{
"codes": [
"Email.userDTO.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"userDTO.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"defaultMessage": ".*",
"codes": [
".*"
],
"arguments": null
}
],
"defaultMessage": "郵箱格式不正確",
"objectName": "userDTO",
"field": "email",
"rejectedValue": "84513654",
"bindingFailure": false,
"code": "Email"
},
{
"codes": [
"IsLeek.userDTO.name",
"IsLeek.name",
"IsLeek.java.lang.String",
"IsLeek"
],
"arguments": [
{
"codes": [
"userDTO.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
},
true
],
"defaultMessage": "此用戶不是韭零后,無法開戶!",
"objectName": "userDTO",
"field": "name",
"rejectedValue": "小綠",
"bindingFailure": false,
"code": "IsLeek"
}
]
以下是所有參數(shù)校驗通過的情況:
POST http://localhost:8080/user/add
Content-Type: application/json
{"name": "新韭菜小綠", "age": 19, "email": "84513654@qq.com"}
{
"name": "新韭菜小綠",
"age": 19,
"email": "84513654@qq.com"
}
我們可能會將UserDTO對象用在不同的接口中接收參數(shù),比如在新增和修改接口中。在新增接口中,不需要校驗userId;在修改接口中需要校驗userId。那注解中的groups字段就派上用場了。groups和@Validated配合能控制哪些注解需不需要開啟校驗。
我們首先定義2個groups分組接口Update和Create,并且繼承Default接口。當然也可以不繼承Default接口,因為使用注解時不顯示指定groups的值,則默認為groups = {Default.class}。所以繼承了Default接口,在用@Validated(Create.class)時,也會校驗groups = {Default.class}的注解。
package com.nobody.annotation;
import javax.validation.groups.Default;
public interface Create extends Default {
}
package com.nobody.annotation;
import javax.validation.groups.Default;
public interface Update extends Default {
}
在用到注解的地方,填寫groups的值。
package com.nobody.dto;
import com.nobody.annotation.Create;
import com.nobody.annotation.IsLeek;
import com.nobody.annotation.Update;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class UserDTO {
@NotBlank(message = "用戶ID不能為空", groups = Update.class)
private String userId;
@NotBlank(message = "姓名不能為空", groups = {Update.class, Create.class})
@IsLeek
private String name;
@Min(value = 18, message = "年齡不能小于18")
private int age;
@NotEmpty(message = "郵箱不能為空")
@Email(message = "郵箱格式不正確")
private String email;
}
最后,在需要聲明校驗的地方,通過@Validated的指定即可。
package com.nobody.controller;
import com.nobody.annotation.Create;
import com.nobody.annotation.Update;
import com.nobody.dto.UserDTO;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
@PostMapping("add")
public Object add(@RequestBody @Validated(Create.class) UserDTO userDTO) {
System.out.println(">>> 用戶開戶成功...");
return userDTO;
}
@PostMapping("update")
public Object update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
System.out.println(">>> 用戶信息修改成功...");
return userDTO;
}
}
調(diào)用add接口時,即使不傳userId也能通過,即不對userId進行校驗。
POST http://localhost:8080/user/add
Content-Type: application/json
{"name": "新韭菜小綠", "age": 18, "email": "84513654@qq.com"}
調(diào)用update接口時,不傳userId,會校驗不通過。
POST http://localhost:8080/user/update
Content-Type: application/json
{"name": "新韭菜小綠", "age": 18, "email": "84513654@qq.com"}
[
{
"codes": [
"NotBlank.userDTO.userId",
"NotBlank.userId",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"userDTO.userId",
"userId"
],
"arguments": null,
"defaultMessage": "userId",
"code": "userId"
}
],
"defaultMessage": "用戶ID不能為空",
"objectName": "userDTO",
"field": "userId",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
}
]
此演示項目已上傳到Github,如有需要可自行下載,歡迎 Star 。 https://github.com/LucioChn/spring
以上就是淺談自定義校驗注解ConstraintValidator的詳細內(nèi)容,更多關(guān)于自定義校驗注解ConstraintValidator的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java GZip 基于磁盤實現(xiàn)壓縮和解壓的方法
這篇文章主要介紹了Java GZip 基于磁盤實現(xiàn)壓縮和解壓,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考需要的朋友可以參考下2020-08-08
Spring學習之動態(tài)代理(JDK動態(tài)代理和CGLIB動態(tài)代理)
本篇文章主要介紹了Spring學習之動態(tài)代理(JDK動態(tài)代理和CGLIB動態(tài)代理),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
提升網(wǎng)絡(luò)請求穩(wěn)定性HttpClient的重試機制深入理解
這篇文章主要為大家介紹了提升網(wǎng)絡(luò)請求穩(wěn)定性HttpClient的重試機制深入理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
SpringBoot2.0+阿里巴巴Sentinel動態(tài)限流實戰(zhàn)(附源碼)
這篇文章主要介紹了SpringBoot2.0+阿里巴巴Sentinel動態(tài)限流實戰(zhàn)(附源碼),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
spring中的@Value讀取配置文件的細節(jié)處理過程
這篇文章主要介紹了spring中的@Value讀取配置文件的細節(jié)處理過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
Springmvc發(fā)送json數(shù)據(jù)轉(zhuǎn)Java對象接收
這篇文章主要介紹了Springmvc發(fā)送json數(shù)據(jù)轉(zhuǎn)Java對象接收,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-10-10

