javascript
SpringBoot从入门到精通教程(二十七)- @Valid注解用法详解+全局处理器Exception优雅处理参数验证用法
問題痛點
用?Spring 框架寫代碼時,寫接口類,相信大家對該類的寫法非常熟悉。在寫接口時要寫效驗請求參數(shù)邏輯,這時候我們會常用做法是寫大量的 if 與 if else 類似這樣的代碼來做判斷,如下所示:
@RestController public class TestController {@PostMapping("/user")public String addUserInfo(@RequestBody User user) {if (user.getName() == null || "".equals(user.getName()) {......} else if(user.getSex() == null || "".equals(user.getSex())) {......} else if(user.getUsername() == null || "".equals(user.getUsername())) {......} else {......}......}}這樣的代碼如果按正常代碼邏輯來說,是沒有什么問題的,不過按優(yōu)雅來說,簡直糟糕透了。不僅不優(yōu)雅,而且如果存在大量的驗證邏輯,這會使代碼看起來亂糟糟,大大降低代碼可讀性。
那么有沒有更好的方法能夠簡化這個過程呢?
答案當(dāng)然是有,推薦的是使用?@Valid?注解來幫助我們簡化驗證邏輯。
Tips技術(shù)點
1. @Valid注解
- 注解 @Valid 的主要作用是用于數(shù)據(jù)效驗,可以在定義的實體中的屬性上,添加不同的注解來完成不同的校驗規(guī)則,而在接口類中的接收數(shù)據(jù)參數(shù)中添加 @valid 注解,這時你的實體將會開啟一個校驗的功能。
2. @Valid 的相關(guān)注解
下面是 @Valid 相關(guān)的注解,在實體類中不同的屬性上添加不同的注解,就能實現(xiàn)不同數(shù)據(jù)的效驗功能
注解名稱 作用描述 @Null 限制只能為null @NotNull 限制必須不為null @AssertFalse 限制必須為false @AssertTrue 限制必須為true @DecimalMax(value) 限制必須為一個不大于指定值的數(shù)字 @DecimalMin(value) 限制必須為一個不小于指定值的數(shù)字 @Digits(integer,fraction) 限制必須為一個小數(shù),且整數(shù)部分的位數(shù)不能超過integer,小數(shù)部分的位數(shù)不能超過fraction @Future 限制必須是一個將來的日期 @Max(value) 限制必須為一個不大于指定值的數(shù)字 @Min(value) 限制必須為一個不小于指定值的數(shù)字 @Past 限制必須是一個過去的日期 @Pattern(value) 限制必須符合指定的正則表達式 @Size(max,min) 限制字符長度必須在min到max之間 @Past 驗證注解的元素值(日期類型)比當(dāng)前時間早 @NotEmpty 驗證注解的元素值不為null且不為空(字符串長度不為0、集合大小不為0) @NotBlank 驗證注解的元素值不為空(不為null、去除首位空格后長度為0),不同于@NotEmpty,@NotBlank只應(yīng)用于字符串且在比較時會去除字符串的空格 @Email 驗證注解的元素值是Email,也可以通過正則表達式和flag指定自定義的email格式3.?使用 @Valid 進行參數(shù)效驗步驟
整個過程如下圖所示,用戶訪問接口,然后進行參數(shù)效驗,因為 @Valid 不支持平面的參數(shù)效驗(直接寫在參數(shù)中字段的效驗)所以基于 GET 請求的參數(shù)還是按照原先方式進行效驗,而 POST 則可以以實體對象為參數(shù),可以使用 @Valid 方式進行效驗。如果效驗通過,則進入業(yè)務(wù)邏輯,否則拋出異常,交由全局異常處理器進行處理。
案例用法
1.?實體類中添加 @Valid 相關(guān)注解
使用?@Valid?相關(guān)注解非常簡單,只需要在參數(shù)的實體類中屬性上面添加如?@NotBlank、@Max、@Min?等注解來對該字段進限制,如下:
User:
public class User {@NotBlank(message = "姓名不為空")private String username;@NotBlank(message = "密碼不為空")private String password; }如果是嵌套的實體對象,則需要在最外層屬性上添加?@Valid?注解:
User:
public class User {@NotBlank(message = "姓名不為空")private String username;@NotBlank(message = "密碼不為空")private String password;//嵌套必須加 @Valid,否則嵌套中的驗證不生效@Valid@NotNull(message = "用戶信息不能為空")private UserInfo userInfo; }UserInfo:
public class User {@NotBlank(message = "年齡不為空")@Max(value = 18, message = "不能超過18歲")private String age;@NotBlank(message = "性別不能為空")private String gender; }2.?接口類中添加 @Valid 注解
在?Controller?類中添加接口,POST?方法中接收設(shè)置了 @Valid 相關(guān)注解的實體對象,然后在參數(shù)中添加?@Valid?注解來開啟效驗功能,需要注意的是,?@Valid?對?Get?請求中接收的平面參數(shù)請求無效,稍微略顯遺憾。
@RestController public class TestController {@PostMapping("/user")public String addUserInfo(@Valid @RequestBody User user) {return "調(diào)用成功!";}}3.?全局異常處理類中處理 @Valid 拋出的異常
最后,我們寫一個全局異常處理類,然后對接口中拋出的異常進行處理,而?@Valid?配合?Spring?會拋出?MethodArgumentNotValidException?異常,這里我們需要對該異常進行處理即可。
package com.md.demo.exception;import java.util.List;import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.util.StringUtils; 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.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice;import com.md.demo.util.JsonResult; import com.md.demo.util.ResultCode;import lombok.extern.slf4j.Slf4j;@Slf4j @RestControllerAdvice("com.md") //指定異常處理的包名 public class GlobalExceptionHandler {/*** 參數(shù)效驗異常處理器** @param e 參數(shù)驗證異常* @return ResponseInfo*/@ResponseStatus(HttpStatus.BAD_REQUEST) //設(shè)置狀態(tài)碼為 400@ExceptionHandler(MethodArgumentNotValidException.class)public JsonResult parameterExceptionHandler(MethodArgumentNotValidException e) {log.error("數(shù)驗證異常", e);// 獲取異常信息BindingResult exceptions = e.getBindingResult();// 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息if (exceptions.hasErrors()) {List<ObjectError> errors = exceptions.getAllErrors();if (!errors.isEmpty()) {// 這里列出了全部錯誤參數(shù),按正常邏輯,只需要第一條錯誤即可FieldError fieldError = (FieldError) errors.get(0);return new JsonResult(ResultCode.PARAM_ERROR, fieldError.getDefaultMessage());}}return new JsonResult(ResultCode.PARAM_ERROR);} }代碼演示
1. 項目目錄結(jié)構(gòu)
2.?pom.xml依賴組件(使用Lombok 包來簡化開發(fā)過程)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.md</groupId><artifactId>spring-boot2-parent</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath></parent><artifactId>spring-boot2-valid</artifactId><packaging>jar</packaging><name>spring-boot2-valid</name><description>Spring Boot, MVC, Rest API for App</description><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 構(gòu)建成可運行的Web項目 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib-ext-spring</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>3. 自定義個異常類
自定義個異常類,方便我們處理 GET 請求(GET 請求參數(shù)中一般是沒有實體對象的,所以不能使用 @Valid),當(dāng)請求驗證失敗時,手動拋出自定義異常,交由全局異常處理。
package com.md.demo.exception;public class ParamaErrorException extends RuntimeException {private static final long serialVersionUID = 1L;public ParamaErrorException() {}public ParamaErrorException(String message) {super(message);}}4. 自定義DTO類中添加 @Valid 相關(guān)注解
GetUserByIdDTO:
package com.md.demo.dto;import javax.validation.constraints.NotEmpty;import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data;@Data @ApiModel("測試-查詢條件") public class GetUserByIdDTO {@ApiModelProperty(value = "id標(biāo)識值", required = true)@NotEmpty(message = "[userId值]不能為空")private String userId;@ApiModelProperty(value = "用戶名")private String userName; }5. Controller 中添加 @Valid 注解(這里我定義了一個BaseDTO基本請求數(shù)據(jù)模型)
GetController:
package com.md.demo.controller;import javax.validation.Valid;import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; 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 com.md.demo.controller.base.BaseDTO; import com.md.demo.dto.GetUserByIdDTO; import com.md.demo.exception.ParamaErrorException; import com.md.demo.util.JsonResult; import com.md.demo.util.ResultCode;import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j;/*** @author Minbo*/ @RestController @RequestMapping("/api/") @Api(tags = { "查詢接口" }) @Slf4j public class GetController {/*** 測試Post請求*/@ApiOperation(value = "TestPost接口", httpMethod = "POST")@PostMapping("/test/post")public JsonResult testPost(@Valid @RequestBody BaseDTO<GetUserByIdDTO> dto) {log.debug("enter test post api...");return new JsonResult(ResultCode.SUCCESS);}/*** 測試Get請求*/@Validated@ApiOperation(value = "TestGet接口", httpMethod = "GET")@GetMapping("/test/get/{userName}")public JsonResult testGet(@PathVariable String userName) {log.debug("enter test get api...");if (userName == null || "".equals(userName)) {throw new ParamaErrorException("userName 不能為空");}return new JsonResult(ResultCode.SUCCESS);}}6. 定義全局異常處理類
這里創(chuàng)建一個全局異常處理類,方便統(tǒng)一處理異常錯誤信息。里面添加了不同異常處理的方法,專門用于處理接口中拋出的異常信息
GlobalExceptionHandler:
package com.md.demo.exception;import java.util.List;import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.util.StringUtils; 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.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice;import com.md.demo.util.JsonResult; import com.md.demo.util.ResultCode;import lombok.extern.slf4j.Slf4j;@Slf4j @RestControllerAdvice("com.md") public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)public JsonResult handleException(Exception e) {log.error("系統(tǒng)異?!救之惓L幚怼?#xff1a;" + e.getMessage(), e);return new JsonResult(ResultCode.SYS_EXCEPTION, "系統(tǒng)異常:" + e.getMessage());}/*** 忽略參數(shù)異常處理器** @param e 忽略參數(shù)異常* @return ResponseResult*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(MissingServletRequestParameterException.class)public JsonResult parameterMissingExceptionHandler(MissingServletRequestParameterException e) {log.error("忽略參數(shù)異常", e);return new JsonResult(ResultCode.PARAM_ERROR, "請求參數(shù) " + e.getParameterName() + " 不能為空");}/*** 缺少請求體異常處理器** @param e 缺少請求體異常* @return ResponseResult*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(HttpMessageNotReadableException.class)public JsonResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {log.error("缺少請求體異常", e);return new JsonResult(ResultCode.PARAM_ERROR, "參數(shù)體不能為空");}/*** 參數(shù)效驗異常處理器** @param e 參數(shù)驗證異常* @return ResponseInfo*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(MethodArgumentNotValidException.class)public JsonResult parameterExceptionHandler(MethodArgumentNotValidException e) {log.error("數(shù)驗證異常", e);// 獲取異常信息BindingResult exceptions = e.getBindingResult();// 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息if (exceptions.hasErrors()) {List<ObjectError> errors = exceptions.getAllErrors();if (!errors.isEmpty()) {// 這里列出了全部錯誤參數(shù),按正常邏輯,只需要第一條錯誤即可FieldError fieldError = (FieldError) errors.get(0);return new JsonResult(ResultCode.PARAM_ERROR, fieldError.getDefaultMessage());}}return new JsonResult(ResultCode.PARAM_ERROR);}/*** 自定義參數(shù)錯誤異常處理器** @param e 自定義參數(shù)* @return ResponseInfo*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler({ ParamaErrorException.class })public JsonResult paramExceptionHandler(ParamaErrorException e) {log.error("自定義參數(shù)參數(shù)", e);// 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息if (!StringUtils.isEmpty(e.getMessage())) {return new JsonResult(ResultCode.PARAM_ERROR, e.getMessage());}return new JsonResult(ResultCode.PARAM_ERROR);}}7. 啟動類
Application:
package com.md.demo;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter;import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;/*** 程序主入口* * @author Minbo**/ @SpringBootApplication @EnableSwaggerBootstrapUI @ComponentScan(basePackages = "com.md") public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}/*** 開啟過濾器功能* * @return*/private CorsConfiguration buildConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");return corsConfiguration;}/*** 跨域過濾器* * @return*/@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", buildConfig());return new CorsFilter(source);} }接口測試
1. 啟動后,訪問地址:http://localhost:9090/doc.html?(已集成了swagger2框架,Swagger集成用法教程)
2. 測試post接口
可以看到在執(zhí)行 POST 請求,也能正常按我們?nèi)之惓L幚砥髦械脑O(shè)置處理異常信息,且提示信息為我們設(shè)置在實體類中的 Message
3. 測試get接口
完整源碼下載
我的Github源碼地址:
https://github.com/hemin1003/spring-boot-study/tree/master/spring-boot2-study/spring-boot2-parent/spring-boot2-valid
下一章教程
SpringBoot從入門到精通教程(二十八)- 動態(tài)修改日志輸出級別用法
該系列教程
SpringBoot從入門到精通教程
?
我的專欄
- SpringBoot系列專欄
- 高可用高并發(fā)實戰(zhàn)專欄
- 微服務(wù)架構(gòu)實戰(zhàn)
- DevOps實戰(zhàn)專欄
- 程序化廣告實戰(zhàn)專欄
?
?
至此,全部介紹就結(jié)束了
?
?
-------------------------------
-------------------------------
?
我的CSDN主頁
關(guān)于我(個人域名)
我的開源項目集Github
?
期望和大家一起學(xué)習(xí),一起成長,共勉,O(∩_∩)O謝謝
歡迎交流問題,可加個人QQ 469580884,
或者,加我的群號?751925591,一起探討交流問題
不講虛的,只做實干家
Talk is cheap,show me the code
總結(jié)
以上是生活随笔為你收集整理的SpringBoot从入门到精通教程(二十七)- @Valid注解用法详解+全局处理器Exception优雅处理参数验证用法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BZOJ2331: 地板 题解
- 下一篇: 11.Kuerbernetes