@Validated和@Valid差別
借鑒文章:大佬文章
防止文章連結會過期,就搬運了!
基礎參數校驗: @Validated
就不過多示範了…
分組:
定義接口 ,根據接口
将不同的校驗規則分給不同的組,在使用時,指定不同的校驗規則
接口類
Group1.Java
package com.example.validateddemo.interfaces;
/**
* 校驗分組1
* @author He Changjie on 2020/9/5
*/
public interface Group1 {
}
Group2.Java
package com.example.validateddemo.interfaces;
/**
* 校驗分組2
* @author He Changjie on 2020/9/5
*/
public interface Group2 {
}
實體類
使用注解時,可以給屬性設定多個校驗進行分組!
User2Dto.Java
package com.example.validateddemo.entity.dto;
import com.example.validateddemo.interfaces.Group1;
import com.example.validateddemo.interfaces.Group2;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class User2Dto {
/**
* 使用者名
*/
@NotBlank(message = "使用者名不能為空!", groups = {Group1.class}) //給屬性設定組!
private String username;
/**
* 性别
*/
@NotBlank(message = "性别不能為空!")
private String gender;
/**
* 年齡
*/
@Min(value = 1, message = "年齡有誤!", groups = {Group1.class}) //設定 Group1
@Max(value = 120, message = "年齡有誤!", groups = {Group2.class}) //設定 Group2
private int age;
/**
* 位址
*/
@NotBlank(message = "位址不能為空!")
private String address;
/**
* 郵箱
*/
@Email(message = "郵箱有誤!", groups = {Group2.class})
private String email;
/**
* 手機号碼 正規表達式...
*/
@Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$",message = "手機号碼有誤!", groups = {Group2.class})
private String mobile;
}
控制類
Controller 中指定了,校驗的組類型!
Demo1Controller.Java
package com.example.validateddemo.controller;
import com.example.validateddemo.base.Result;
import com.example.validateddemo.entity.dto.Team1Dto;
import com.example.validateddemo.entity.dto.User1Dto;
import com.example.validateddemo.entity.dto.User2Dto;
import com.example.validateddemo.interfaces.Group1;
import com.example.validateddemo.interfaces.Group2;
import com.example.validateddemo.utils.ResultUtil;
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;
/**
* @author He Changjie on 2020/9/5
*/
@RestController
@RequestMapping("/api/v1")
public class Demo1Controller {
//不指定分組,即是預設組 或 Default.Class 即對沒有進行分組的JSR303 資料進行校驗!
@PostMapping("/insert3")
public Result validatedDemo3(@Validated @RequestBody User2Dto user2Dto){
return ResultUtil.success(user2Dto);
}
@PostMapping("/insert4")
public Result validatedDemo4(@Validated(Group1.class) @RequestBody User2Dto user2Dto){
return ResultUtil.success(user2Dto);
}
@PostMapping("/insert5")
public Result validatedDemo5(@Validated(Group2.class) @RequestBody User2Dto user2Dto){
return ResultUtil.success(user2Dto);
}
}
測試:
檢視insert4
使用了Group1 的組校驗~

檢視insert5
使用了Group2 的組校驗~
不指定組使用預設組進行校驗!
則,沒有進行分組的JSR303 注解生效進行校驗通過!
總結:
@Validated
- 注解,可以使用 分組進行校驗!
- 定義分組接口,根據接口來給實體類上的
校驗注解進行分組!
groups = {組接口.class}
- Controller 上使用時候可以指定,校驗實體的組
不指定即沒有組的校驗進行校驗核對!
@Validated(組接口.class)
嵌套驗證:@Valid
實體:
在比較兩者嵌套驗證時,先說明下什麼叫做嵌套驗證。比如我們現在有個實體叫做Item:
Item帶有很多屬性,屬性裡面有屬性id,屬性值id,屬性名和屬性值,如下所示:
- 其中包含一個List類型的資料
或其它引用類型!
Item.Java
public class Item {
@NotNull(message = "id不能為空")
@Min(value = 1, message = "id必須為正整數")
private Long id;
@NotNull(message = "props不能為空")
@Size(min = 1, message = "至少要有一個屬性")
private List<Prop> props;
}
Prop.Java
public class Prop {
@NotNull(message = "pid不能為空")
@Min(value = 1, message = "pid必須為正整數")
private Long pid;
@NotNull(message = "vid不能為空")
@Min(value = 1, message = "vid必須為正整數")
private Long vid;
@NotBlank(message = "pidName不能為空")
private String pidName;
@NotBlank(message = "vidName不能為空")
private String vidName;
}
-
Prop
屬性
屬性這個實體也有自己的驗證機制,比如屬性和屬性值id不能為空,屬性名和屬性值不能為空等
控制類:
ItemController.Java
@RestController
public class ItemController {
@RequestMapping("/item/add")
public void addItem(@Validated Item item, BindingResult bindingResult) {
doSomething();
}
}
-
程式校驗時候:
如果Item實體的props屬性不額外加注釋,隻有
和@NotNull
無論入參采用@Size
還是@Validated
@Valid
驗證
Spring Validation架構隻會對Item的id和props做非空和數量驗證,
不會對props字段裡的Prop實體進行字段驗證
-
也就是@Validated和@Valid加在方法參數前,都不會自動對參數進行嵌套驗證。
也就是說如果傳的List中有Prop的pid為空或者是負數,入參驗證不會檢測出來。
更改:實體:
為了能夠進行嵌套驗證,必須手動在Item實體的props字段上明确指出這個字段裡面的實體也要進行驗證。
- 由于@Validated不能用在成員屬性(字段)上
- 但是@Valid能加在成員屬性(字段)上,而且@Valid類注解上也說明了它支援嵌套驗證功能
由此推斷:
- @Valid加在方法參數時并不能夠自動進行嵌套驗證
- 而是用在需要嵌套驗證類的相應字段上,來配合方法參數上@Validated或@Valid來進行嵌套驗證
Item.Java
public class Item {
@NotNull(message = "id不能為空")
@Min(value = 1, message = "id必須為正整數")
private Long id;
@Valid // 嵌套驗證必須用@Valid
@NotNull(message = "props不能為空")
@Size(min = 1, message = "props至少要有一個自定義屬性")
private List<Prop> props;
}
然後我們在ItemController的addItem函數上再使用@Validated或者@Valid,就能對Item的入參進行嵌套驗證。
總結:
嵌套驗證:
- 就是說,注解隻能對,實體普通屬性進行校驗,如果是引用類型,且也是一個對象類型
注解并不會自動的進行,校驗内部的元素!
-
@Validated和@Valid加在方法參數前,都不會自動對參數進行嵌套驗證
由于@Validated不能用在成員屬性(字段)上
@Valid能加在成員屬性(字段)上,而且@Valid類注解上也說明了它支援嵌套驗證功能。
- 是以,如果校驗需要在實作上對象,進行嵌套驗證明體内部的對象,可以使用
對實體屬性進行嵌套校驗!@Valid
異常處理:
BindingResult
Controller控制層寫參數接收的入口
-
需要注意的是@Valid 和 BindingResult 是一 一對應的, 如果有多個@Valid
那麼每個@Valid後面都需要添加BindingResult用于接收bean中的校驗資訊.
配套使用!
随意一個Controller
@RequestMapping(value = "/test", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody ResponseEntity<Pesponsibles> testBindingResult(@Valid @RequestBody Parameter parameter,BindingResult bindingResult)
{
log.info("test start");
Pesponsibles pesponsibles=new Pesponsibles();
//判斷是否存在,校驗異常資料!列印日志!
if(bindingResult.hasErrors()){
List<FieldError> fieldErrors = bindingResult.getFieldErrors(); //擷取校驗異常集合!
//周遊 輸出日志!
fieldErrors.forEach(fieldError -> {
//日志列印不符合校驗的字段名和錯誤提示
log.error("error field is : {} ,message is : {}", fieldError.getField(), fieldError.getDefaultMessage());
});
//控制台檢視!
for(int i=0;i<fieldErrors.size();i++){
//控制台列印不符合校驗的字段名和錯誤提示
System.out.println("error field is :"+fieldErrors.get(i).getField()+",message is :"+fieldErrors.get(i).getDefaultMessage());
}
//傳回前台資料異常!這裡就根據自己項目情況而定
return new ResponseEntity<>(pesponsibles, HttpStatus.BAD_REQUEST);
}
//沒有異常操作...👍
return new ResponseEntity<>(pesponsibles, HttpStatus.OK);
}
總結:
@Valid / @Validated 要和
BindingResult
搭配使用,用來輸出擷取校驗失敗的資料,傳回前端。
- 如果是使用Spring表單 還可以可以Spring表單進行綁定使用展示異常資訊,
目前少見了!
- 根據實際開發需求來做,講異常資訊包裝傳回前端進行展示,
提示使用者!
全局異常處理類:
可以先了解:此篇文章:異常處理!
因為每個Controller 都會需要進行
BindingResult
可能會比較麻煩,可以使用全局異常進行捕獲處理!
全局處理異常類
package com.example.validateddemo.handler;
import com.example.validateddemo.base.Result;
import com.example.validateddemo.enums.ResultEnum;
import com.example.validateddemo.utils.ResultUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
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 org.springframework.web.bind.annotation.ResponseStatus;
import java.util.List;
@Slf4j
@ControllerAdvice
public class ValidatedExceptionHandler {
/**
* 處理@Validated參數校驗失敗異常
* @param exception 異常類
* @return 響應
*/
@ResponseBody
//@ResponseStatus的作用就是為了改變HTTP響應的狀态碼
@ResponseStatus(HttpStatus.BAD_REQUEST) //改變響應時候 HttpStatus狀态 400 接口異常!
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result exceptionHandler(MethodArgumentNotValidException exception){
BindingResult result = exception.getBindingResult();
StringBuilder stringBuilder = new StringBuilder();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
if (errors != null) {
errors.forEach(p -> {
FieldError fieldError = (FieldError) p;
log.warn("Bad Request Parameters: dto entity [{}],field [{}],message [{}]",fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
stringBuilder.append(fieldError.getDefaultMessage());
});
}
}
return ResultUtil.validatedException(stringBuilder.toString());
}
}
@ControllerAdvice 注解
@ControllerAdvice ,很多初學者可能都沒有聽說過這個注解
-
這是一個非常有用的注解,顧名思義,這是一個增強的 Controller。用這個可以實作三個方面的功能:
全局異常處理
全局資料綁定
全局資料預處理
+ @ExceptionHandler 實作全局異常處理!
注解聲明異常類型, 當類中出現改異常會進入該方法處理
- 在單個,Controller中也可以使用,但是進限于聲明的Controller
- 聲明在
則全局的Controller 都會有這個方法,任何地方出現異常都會走到這方法中!@ControllerAdvice類中
全局異常
當将異常抛到controller時,可以對異常進行統一處理:
- 規定傳回的json格式
- 或 跳轉到一個錯誤頁面
@ControllerAdvice
public class MyGlobalExceptionHandler {
//注解捕獲, Exception類型異常, 即所有的異常都将捕獲!
@ExceptionHandler(Exception.class)
public ModelAndView customException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("message", e.getMessage());
mv.setViewName("myerror");
return mv;
}
}
在該類中,可以定義多個方法,不同的方法處理不同的異常
- 例如專門處理空指針的方法
- 專門處理數組越界的方法…
- 也可以直接向上面代碼一樣,在一個方法中處理所有的異常資訊。
- 學習後續擴充!
總結:@ControllerAdvice
就相當于一個全局的Controller 累下的方法,可以被所有的Controller類共享…可以做
全局異常
,
初始化資料
資料綁定!