導讀
? ? ? ? 在 JavaEE 項目中, RestFull 層接收參數(shù)首先要對一些字段的格式進行校驗,以防止所有查詢都落到數(shù)據(jù)庫,這也是一種合理的限流手段。以前基本上都是用 if...else...,這樣的代碼太啰嗦,除了使用策略模式進行優(yōu)化,今天介紹一下校驗注解@Valid,@Validated和@PathVariable,不僅可以減輕代碼量,還加強了代碼的易讀性。
正文
1. @Valid 和 @Validated 區(qū)別
????????先講一下這兩個注解:@Valid與@Validated都是用來校驗接收參數(shù)的,如果不使用注解校驗參數(shù),那么就需要在業(yè)務代碼中逐一校驗,這樣會增加很多的工作量,并且代碼不優(yōu)美。
????????剛開始接觸的時候多半會被弄混,實際上二者差距還是挺大的。根據(jù)自己的項目經(jīng)驗,@Validated和@Valid各有特點,可以聯(lián)合使用。
- 提供者
javax.validation.Valid:使用 Hibernate validation 的時候使用,是 JSR-303 規(guī)范標準注解支持。如果你是 springboot 項目,那么可以不用單獨引入依賴了,因為它就存在于最核心的 web 開發(fā)包(spring-boot-starter-web)里面;
org.springframework.validation.annotation.Validated:只用 Spring Validator 校驗機制使用,是 Spring 做得一個自定義注解,增強了分組功能;
- 標注位置
@Validated:可以用在類型、方法和方法參數(shù)上,不能用于成員屬性(field)上。如果注解在成員屬性上,則會報不適用于field的錯誤;? ? ? ? ? ? ? ? ??
@Valid:可以用在方法、構造函數(shù)、方法參數(shù)和成員屬性(field)上;
- 分組支持
@Validated:提供分組功能,可以在參數(shù)驗證時,根據(jù)不同的分組采用不同的驗證機制;
@Valid:沒有分組的功能,不能進行分組校驗;
-
嵌套支持
@Validated:不能進行嵌套對象校驗;
@Valid:可以進行嵌套校驗,但是,需要在嵌套的字段上面加上注解;
2. 常用的校驗方法
- Debug進入jar包,可以看到全量的相關注解:
- 簡述一些常用注解:
注解 | 使用方法 |
---|---|
@AssertFalse | 被校驗的對象必須為 true。
|
@AssertTrue | 被校驗的對象必須為?false。 |
@DecimalMax(value = "val") | 被校驗的對象必須是數(shù)字,而且小于等于 val。
|
@DecimalMin(value = "val") | 被校驗的對象必須是數(shù)字,而且大于等于 val。
|
@Digits(integer = in, fraction = fra) | 校驗字符串是否是符合指定格式的數(shù)字:in 指定整數(shù)精度,fra 指定小數(shù)精度。 |
@Future | 被校驗的對象(日期類型)必須是將來時間,即:比當前時間晚。 |
@Past | :被校驗的對象(日期類型)必須是過去時間,即:比當前時間早。 |
@Size(min = min, max = max) | 元素值的在 min 和 max(包含)指定區(qū)間之內(nèi),如字符長度、集合大?。▽τ诩蟻碚f,null 和空字符串都是算長度的)。 |
@NotBlank |
所注解的元素不能為null且不能為空白,并且必須至少包含一個非空白字符,用于校驗CharSequence(含String、StringBuilder和StringBuffer)。只支持字符類型。 |
@NotEmpty |
所注解的元素不能為null且長度大于0,可以是空白,用于校驗 CharSequence、數(shù)組、Collection 和 Map。 |
@NotNull |
所注解的元素不能為 null,接受任何類型。 |
@Null |
所注解的元素必須為 null,接受任何類型。 |
@Pattern(regexp = "正則表達式", message = "") | 所注解的元素必須匹配指定的正則表達式。 注意:如果 @Pattern 所注解的元素是null,則@Pattern 注解會返回 true,即也會通過校驗,所以應該把 @Pattern 注解和 @NotNull 注解結(jié)合使用。 |
3. @Validated分組校驗
場景:多個 Restfull 接口共用一個標準 Bean,每個接口的參數(shù)相同,但是需要校驗的參數(shù)(必輸項)卻不完全相同,這樣的場景可以使用?@Validated,因為它提供了分組校驗的功能。
分組 | 說明 |
---|---|
隱式分組 |
1.沒有顯式分組的默認都是 Default 組; 2.顯式分組之后,剩下的那些沒有被劃分到自建組的字段都屬于 Default 組; 3.平常我們寫? |
顯式分組 |
1.自定義interface接口的分組,屬于自建組; 2.自建組可以繼承 Default.class,也可以不繼承?Default.class,兩者意義不同; 3.多個分組可以一起實用; 4.分組機制讓我們可以很靈活的使用對象里面的某些字段,以實現(xiàn)高權限等級參數(shù)傳遞校驗等操作。 |
-
新建請求對象
@Data
public class TeacherDTO {
@NotBlank(message = "id必傳")
private String id;
@NotBlank(message = "不能沒有名稱")
private String name;
@NotNull(message = "age必傳")
private Integer age;
@NotBlank(message = "不能沒有idCard")
private String idCard;
@NotBlank(message = "老師不能沒有手機號", groups = OnlyTeacher.class)
private String phone;
@NotEmpty(message = "學生不能沒有書")
@Size(min = 2, message = "學生必須有兩本書", groups = OnlyStudent.class)
private List<String> bookNames;
@NotEmpty
@Size(min = 1, message = "老師不能沒有學生", groups = TeacherWithDefault.class)
private List<String> studentList;
}
-
新建分組
// Teacher分組
public interface TeacherValid { }
// Student分組
public interface StudentValid { }
// 繼承Default的分組
public interface OtherValid extends Default{ }
-
接口測試
/**
* Created by tjm on 2022/11/11.
*/
@RestController
@RequestMapping("/test")
public class TestValidController {
private static final Logger LOGGER = LoggerFactory.getLogger(TestValidController.class);
/**
* 測試 - 分組校驗 - 默認default
*/
@PostMapping("/only/default")
public Object testDefaultValid(@Validated TeacherDTO param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
}
return ResultGenerator.genSuccessResult();
}
/**
* 測試 - 分組校驗 - 只有teacher
*/
@PostMapping("/only/teacher")
public Object testOnlyTeacherValid(@Validated(OnlyTeacher.class) TeacherDTO param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
}
return ResultGenerator.genSuccessResult();
}
/**
* 測試 - 分組校驗 - 只有student
*/
@PostMapping("/only/student")
public Object testOnlyStudentValid(@Validated(OnlyStudent.class) TeacherDTO param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
}
return ResultGenerator.genSuccessResult();
}
/**
* 測試 - 分組校驗 - teacher + default
*/
@PostMapping("/with/teacher")
public Object testWithTeacherValid(@Validated({OnlyTeacher.class, Default.class}) TeacherDTO param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
}
return ResultGenerator.genSuccessResult();
}
/**
* 測試 - 分組校驗 - 繼承default
*/
@PostMapping("/with/default")
public Object testWithDefaultValid(@Validated(TeacherWithDefault.class) TeacherDTO param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
}
return ResultGenerator.genSuccessResult();
}
}
-
結(jié)果
分組 | 校驗參數(shù) |
---|---|
只有 default | id、name、age、idCard |
只有 teacher | phone |
只有 student | booknames |
teacher + default | id、name、age、idCard、phone |
teacher 繼承 default | id、name、age、idCard、studentList |
4.@Valid嵌套校驗
- 新建請求對象
public class Item {
@NotNull(message = "id不能為空")
@Min(value = 1, message = "id必須為正整數(shù)")
private Long id;
// 嵌套驗證必須用 @Valid
@Valid
@NotNull(message = "props不能為空")
@Size(min = 1, message = "props至少要有一個自定義屬性")
private List<Prop> props;
}
public class Prop {
@NotNull(message = "pid不能為空")
@Min(value = 1, message = "pid必須為正整數(shù)")
private Long pid;
@NotNull(message = "vid不能為空")
@Min(value = 1, message = "vid必須為正整數(shù)")
private Long vid;
@NotBlank(message = "pidName不能為空")
private String pidName;
@NotBlank(message = "vidName不能為空")
private String vidName;
}
- 接口測試
/**
* 測試 - 分組校驗 - 繼承default
*/
@PostMapping("/item")
public Object testItemValid(@Validated Item param, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());
}
return ResultGenerator.genSuccessResult();
}
- 測試結(jié)果
1. 不僅校驗 Item 參數(shù),還會校驗子類 Prop 參數(shù);
2. 注意:嵌套驗證必須在子參數(shù)上用 @Valid。
5.Restfull層@Validated的使用
????????校驗參數(shù)的時候,如何判斷并返回失敗的結(jié)果?一般有兩種方式:
- 全局異常捕獲
@ControllerAdvice
@RestController
@Slf4j
public class GlobalExceptionHandler {
/**
* 非法參數(shù)驗證異常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(value = HttpStatus.OK)
public ApiResult handleMethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
List<String> list = new ArrayList<>();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
list.add(fieldError.getDefaultMessage());
}
Collections.sort(list);
log.error("fieldErrors" + JSON.toJSONString(list));
return ApiResult.fail(ApiCode.PARAMETER_EXCEPTION, list);
}
}
- 用 BindingResult 在實體類校驗信息返回結(jié)果綁定
????????即使是全局異常捕獲的方式,也能看到:校驗信息是被封裝在?BindingResult 對象里的,所以,我們也可以在 RestFull 層直接取。
1.?BindingResult用在實體類校驗信息返回結(jié)果綁定;
2.?BindingResult.hasErrors()判斷是否校驗通過,bindingResult.getFieldError().getDefaultMessage() 獲取在 TestEntity 的屬性設置的自定義message,如果沒有設置,則返回默認值 "javax.validation.constraints.XXX.message"。文章來源:http://www.zghlxwxcb.cn/news/detail-443618.html
? ? ? ? 可以看到,我上面的例子用的都是這種方法,我覺得這樣更方便、直觀,維護性更好。文章來源地址http://www.zghlxwxcb.cn/news/detail-443618.html
到了這里,關于Java代碼瘦身,巧用 @Valid,@Validated 的分組校驗和嵌套檢驗,實現(xiàn)高階參數(shù)校驗操作的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!