天天看點

@Validated和@Valid差別@Validated和@Valid差別異常處理:@ControllerAdvice 注解

@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 的組校驗~

@Validated和@Valid差別@Validated和@Valid差別異常處理:@ControllerAdvice 注解

檢視insert5

使用了Group2 的組校驗~

@Validated和@Valid差別@Validated和@Valid差別異常處理:@ControllerAdvice 注解

不指定組使用預設組進行校驗!

則,沒有進行分組的JSR303 注解生效進行校驗通過!

@Validated和@Valid差別@Validated和@Valid差別異常處理:@ControllerAdvice 注解

總結:

@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
  • 聲明在

    @ControllerAdvice類中

    則全局的Controller 都會有這個方法,任何地方出現異常都會走到這方法中!

全局異常

當将異常抛到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類共享…可以做

全局異常

初始化資料

資料綁定!

繼續閱讀