@Valid和@Validated注解校驗以及異常處理方式
前言
在Javaweb的開發(fā)中,為了防止懂技術(shù)的人對數(shù)據(jù)庫的惡意攻擊,我們通常使用參數(shù)校驗對無效數(shù)據(jù)進(jìn)行篩選,Java生態(tài)下的@valid注解配置SpringBoot的使用可以方便快速的完成對數(shù)據(jù)校驗的各種場景。
同時數(shù)據(jù)校驗分為前端校驗和后端校驗。
可為何前端做完校驗之后,還要在后端進(jìn)行校驗?
如果有人拿到了url地址,使用第三方測試工具比如postman就可以跳過前端頁面的參數(shù)檢驗,所以為了數(shù)據(jù)庫數(shù)據(jù)正確性,我們十分有必要對傳來的數(shù)據(jù)在后端進(jìn)行第二次校驗
一、@Valid注解
1、源碼解析
通過源碼可以看出:
@Valid注解可以作用于:方法、屬性(包括枚舉中的常量)、構(gòu)造函數(shù)、方法的形參上。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}關(guān)于注解源碼解析,可以參考如下鏈接:
2、所屬的包
import javax.validation.Valid;
3、參數(shù)校驗使用注解
(1)空校驗
| 注解 | 應(yīng)用 |
|---|---|
| @Null | 用于基本類型上,限制只能為null |
| @NotNull | 用在基本類型上;不能為null,但可以為empty,沒有Size的約束 |
| @NotEmpty | 用在集合類上面;不能為null,而且長度必須大于0 |
| @NotBlank | 只能作用在String上,不能為null,而且調(diào)用trim()后,長度必須大于0 |
插播一條小內(nèi)容?。?!null和empty有何區(qū)別?
String a = new String String b = "" String c = null
- 此時a是分配了內(nèi)存空間,但值為空,是絕對的空,是一種有值(值存在為空而已)
- 此時b是分配了內(nèi)存空間,值為空字符串,是相對的空,是一種有值(值存在為空字串)
- 此時c是未分配內(nèi)存空間,無值,是一種無值(值不存在)
(2)Boolean校驗
| 注解 | 應(yīng)用 |
|---|---|
| @AssertFalse | 限制必須為false |
| @AssertTrue | 限制必須為true |
(3)長度校驗
| 注解 | 應(yīng)用 |
|---|---|
| @Size(max,min) | 驗證對象(Array,Collection,Map,String)長度是否在給定的范圍之內(nèi) |
| @Length(min=, max=) | 驗證字符串長度是否在給定的范圍之內(nèi) |
(4)日期校驗
| 注解 | 應(yīng)用 |
|---|---|
| @Past | 限制必須是一個過去的日期,并且類型為java.util.Date |
| @Future | 限制必須是一個將來的日期,并且類型為java.util.Date |
| @Pattern(value) | 限制必須符合指定的正則表達(dá)式 |
(5)數(shù)值校驗
建議使用在Stirng,Integer類型,不建議使用在int類型上,因為表單值為“”時無法轉(zhuǎn)換為int,但可以轉(zhuǎn)換為Stirng為"",Integer為null。
| 注解 | 應(yīng)用 |
|---|---|
| @Min(value) | 驗證 Number 和 String 對象必須為一個不小于指定值的數(shù)字 |
| @Max(value) | 驗證 Number 和 String 對象必須為一個不大于指定值的數(shù)字 |
| @DecimalMax(value) | 限制必須為一個不大于指定值的數(shù)字,小數(shù)存在精度 |
| @DecimalMin(value) | 限制必須為一個不小于指定值的數(shù)字,小數(shù)存在精度 |
| @Digits(integer,fraction) | 限制必須為一個小數(shù),且整數(shù)部分的位數(shù)不能超過integer,小數(shù)部分的位數(shù)不能超過fraction |
| @Digits | 驗證 Number 和 String 的構(gòu)成是否合法 |
| @Range(max =3 , min =1 , message = " ") | Checks whether the annotated value lies between (inclusive) the specified minimum and maximum |
Max和Min是對你填的“數(shù)字”是否大于或小于指定值,這個“數(shù)字”可以是number或者string類型。長度限制用length。
(6)其他校驗
| 注解 | 應(yīng)用 |
|---|---|
| 驗證注解的元素值是Email,也可以通過正則表達(dá)式和flag指定自定義的email |
4、具體使用
使用 @Valid 進(jìn)行參數(shù)效驗步驟:
- 實體類中添加 @Valid 相關(guān)注解
- 接口類中添加 @Valid 注解
- 全局異常處理類中處理 @Valid 拋出的異常
運行流程:
整個過程如下圖所示,用戶訪問接口,然后進(jìn)行參數(shù)效驗,因為 @Valid 不支持平面的參數(shù)效驗(直接寫在參數(shù)中字段的效驗)所以基于 GET 請求的參數(shù)還是按照原先方式進(jìn)行效驗,而 POST 則可以以實體對象為參數(shù),可以使用 @Valid 方式進(jìn)行效驗。
如果效驗通過,則進(jìn)入業(yè)務(wù)邏輯,否則拋出異常,交由全局異常處理器進(jìn)行處理。

代碼實踐:
(1)添加maven依賴(三種方式添加依賴)
<!--第一種:valid依賴--> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>版本號</version> </dependency> <!-- 第二種:集成于web依賴中(注意版本號)--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.5.RELEASE</version> </dependency> <!-- 第三種:springboot的validation--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
(2)創(chuàng)建request實體類
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不為空")
private String name;
@Length(max = 3,message = "address最大長度是3")
private String address;
@Max(value = 5,message = "reqNo最大值是5")
private String reqNo;
}(3)創(chuàng)建controller
@RestController
public class ValidTestController {
@RequestMapping("/valid/test")
public void test(@Valid @RequestBody TestRequest request){
System.out.println(request);
}(4)postman測試

postman返回結(jié)果:
{
"timestamp": "2022-11-12T09:54:24.202+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.controller.ValidTestController.test(com.example.domain.TestRequest) with 3 errors: [Field error in object 'testRequest' on field 'name': rejected value []; codes [NotBlank.testRequest.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.name,name]; arguments []; default message [name]]; default message [name不為空]] [Field error in object 'testRequest' on field 'reqNo': rejected value [8]; codes [Max.testRequest.reqNo,Max.reqNo,Max.java.lang.String,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.reqNo,reqNo]; arguments []; default message [reqNo],5]; default message [reqNo最大值是5]] [Field error in object 'testRequest' on field 'address': rejected value [gtyjh]; codes [Length.testRequest.address,Length.address,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [testRequest.address,address]; arguments []; default message [address],3,0]; default message [address最大長度是3]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:141)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:681)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:764)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\r\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
"message": "Validation failed for object='testRequest'. Error count: 3",
"errors": [
{
"codes": [
"NotBlank.testRequest.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"testRequest.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "name不為空",
"objectName": "testRequest",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
},
{
"codes": [
"Max.testRequest.reqNo",
"Max.reqNo",
"Max.java.lang.String",
"Max"
],
"arguments": [
{
"codes": [
"testRequest.reqNo",
"reqNo"
],
"arguments": null,
"defaultMessage": "reqNo",
"code": "reqNo"
},
5
],
"defaultMessage": "reqNo最大值是5",
"objectName": "testRequest",
"field": "reqNo",
"rejectedValue": "8",
"bindingFailure": false,
"code": "Max"
},
{
"codes": [
"Length.testRequest.address",
"Length.address",
"Length.java.lang.String",
"Length"
],
"arguments": [
{
"codes": [
"testRequest.address",
"address"
],
"arguments": null,
"defaultMessage": "address",
"code": "address"
},
3,
0
],
"defaultMessage": "address最大長度是3",
"objectName": "testRequest",
"field": "address",
"rejectedValue": "gtyjh",
"bindingFailure": false,
"code": "Length"
}
],
"path": "/valid/test"
}從后端返回給postman的結(jié)果可以看出,三個字段的校驗都已經(jīng)實現(xiàn)。但是,特別情況,RequestBody可能是嵌套的實體,這個時候,對于嵌套的實體類來說,嵌套必須加 @Valid,如果只在字段上添加校驗注解嵌套中的驗證不生效。
如果只在嵌套類字段上加上校驗注解,如下:
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不為空")
private String name;
@Length(max = 3, message = "address最大長度是3")
private String address;
@Max(value = 5, message = "reqNo最大值是5")
private String reqNo;
private TestRequestInner inner;
@Data
@NoArgsConstructor
public static class TestRequestInner {
@Length(max = 3, message = "最大長度是3")
private String sonName;
private Integer sonAge;
private String schoolNo;
}
}postman測試:

控制臺打?。?/p>

可以看出,嵌套類中sonName的長度校驗并沒有起到作用。
在嵌套類的外層加上@Valid注解,如下:
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不為空")
private String name;
@Length(max = 3, message = "address最大長度是3")
private String address;
@Max(value = 5, message = "reqNo最大值是5")
private String reqNo;
@Valid
private TestRequestInner inner;
// 即使放在list集合里面仍然是需要加上 @Valid 注解
// @Valid
// private List<TestRequestInner> inner;
@Data
@NoArgsConstructor
public static class TestRequestInner {
@Length(max = 3, message = "最大長度是3")
private String sonName;
private Integer sonAge;
private String schoolNo;
}
}postman測試:
校驗成功。

5、異常處理
剛才的測試我們看到,校驗注解全部生效,但是所有的異常全部拋出給postman,從控制臺可以看出:

程序拋出了MethodArgumentNotValidException異常信息,在實際業(yè)務(wù)中,有時候需要處理這個異常,這個時候就需要一個全局異常處理類中處理 @Valid 拋出的異常。
拋出的異常結(jié)構(gòu):

代碼如下:
實體類如上不變,controller接口方法改為返回string:
@RequestMapping("/valid/test")
public String test(@Valid @RequestBody TestRequest request){
System.out.println(request);
return "success";
}異常處理類:
我們可以根據(jù)上圖拋出的異常結(jié)構(gòu),去get我們想要獲得的內(nèi)容。
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 自定義驗證異常
* MethodArgumentNotValidException 方法參數(shù)無效異常
*/
@ResponseStatus(HttpStatus.BAD_REQUEST) //設(shè)置狀態(tài)碼為 400
@ExceptionHandler({MethodArgumentNotValidException.class})
public String paramExceptionHandler(MethodArgumentNotValidException e) {
BindingResult exceptions = e.getBindingResult();
// 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息
if (exceptions.hasErrors()) {
List errors = exceptions.getAllErrors();
if (!errors.isEmpty()) {
// 這里列出了全部錯誤參數(shù),按正常邏輯,只需要第一條錯誤即可
FieldError fieldError = (FieldError) errors.get(0);
return fieldError.getDefaultMessage();
}
}
return "請求參數(shù)錯誤";
}
}postman測試:

6、springboot項目中的異常處理
上述的異常處理只是一個簡單的string返回,但是在實際項目中,返回結(jié)構(gòu)是固定的,下面對于固定的返回結(jié)構(gòu),做異常處理。
(1)request實體類
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Data
@NoArgsConstructor
public class TestRequest {
@NotBlank(message = "name不為空")
private String name;
@Length(max = 3, message = "address最大長度是3")
private String address;
@Max(value = 5, message = "reqNo最大值是5")
private String reqNo;
@Valid
private List<TestRequestInner> inner;
@Data
@NoArgsConstructor
public static class TestRequestInner {
@Length(max = 3, message = "最大長度是3")
private String sonName;
private Integer sonAge;
@NotBlank(message = "schoolNo不空")
private String schoolNo;
}
}(2)結(jié)果返回實體類
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class ResponseResult {
private List<ProvideInfo> provideInfos;
@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public static class ProvideInfo {
private String code;
private String detail;
}
}(3)controller接口方法
import com.example.domain.ResponseResult;
import com.example.domain.TestRequest;
import org.springframework.http.ResponseEntity;
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
public class ValidTestController {
@RequestMapping("/valid/test")
public ResponseEntity<ResponseResult> test(@Valid @RequestBody TestRequest request) {
System.out.println(request);
return null;
}
}(4)postman測試

再插播一個小插曲!?。?!
剛開始寫的結(jié)果返回實體類中的provideInfo是這樣式兒滴:

然后運行項目就出了這個錯:

英文版是這樣式兒滴:

上網(wǎng)查了一下,問題出現(xiàn)在這:

原因:
本應(yīng)當(dāng)(只能)使用無參構(gòu)造器,但編譯器卻發(fā)現(xiàn)代碼中使用了有參(全參)構(gòu)造器,這個全參構(gòu)造器出現(xiàn)在Builder類的build()方法中,該方法試圖調(diào)用一個全參構(gòu)造器。
這個報錯信息表明,@NoArgsConstructor抑制了@Builder生成全參構(gòu)造器,只生成了一個無參構(gòu)造器,使用lombok插件delombok @Builder和@NoArgsConstructor兩個注解,可以證實這一抑制現(xiàn)象。
解決方法:
一種方法是同時使用@Builder、@NoArgsConstructor和@AllArgConstructor,還有一種方法是顯式添加@Tolerate注解的無參構(gòu)造器。
然后在provideInfo上添加了@AllArgConstructor注解(如下圖),成功運行!

(5)全局異常處理類各種形式
對于全局異常類的處理,涉及到攔截器相關(guān)內(nèi)容,這里不做多說。全局異常類的處理方式有很多種:
方式一:
// Enum枚舉類
public enum CodeEnum {
// 根據(jù)自己的項目需求更改狀態(tài)碼,這里只是一個示范
UNKNOW_EXCEPTION(10000,"系統(tǒng)未知錯誤"),
VALID_EXCETIPON(10001,"參數(shù)格式校驗錯誤");
private int code;
private String msg;
CodeEnum(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
//異常處理類
@Slf4j
@RestControllerAdvice("com.cbj.db_work.controller") //表明需要處理異常的范圍
public class GlobalExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class) // 參數(shù)異常拋出的異常類型為MethodArgumentNotValidException,這里捕獲這個異常
// R為統(tǒng)一返回的處理類
public R validExceptionHandler(MethodArgumentNotValidException e){
System.out.println("數(shù)據(jù)異常處理");
log.error("數(shù)據(jù)校驗出現(xiàn)問題,異常類型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
String message = item.getDefaultMessage();
// 獲取錯誤的屬性字段名
String field = item.getField();
map.put(field,message);
});
return R.error().code(CodeEnum.VALID_EXCETIPON.getCode()).message(CodeEnum.VALID_EXCETIPON.getMsg()).data("errorData",map);
}
}方式二:
@ControllerAdvice
@RestControllerAdvice
@Slf4j
public class ValidExceptionHandler extends GlobalExceptionHandler {
// GET請求參數(shù)異常處理
@ExceptionHandler(value = ConstraintViolationException.class)
public Result<Object> constraintViolationExceptionHandler(ConstraintViolationException e) {
StringBuilder msg = new StringBuilder();
Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
for (ConstraintViolation<?> constraintViolation : constraintViolations) {
String message = constraintViolation.getMessage();
msg.append(message).append(";");
}
return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg.toString());
}
@ExceptionHandler(ArithmeticException.class)
public Result<Object> arithmeticExceptionHandler(ArithmeticException e) {
e.printStackTrace();
return ResultResponse.getFailResult(ResultCode.NOT_FOUND.getResultCode(), "算術(shù)異常!"+e.getMessage());
}
// POST請求參數(shù)異常處理
@ExceptionHandler(BindException.class)
public Result<Object> bindExceptionHandler(BindException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
String msg;
if (Objects.isNull(fieldError)) {
msg = "POST請求參數(shù)異常:" + JSON.toJSONString(e.getBindingResult());
log.info(msg);
} else {
msg = fieldError.getDefaultMessage();
}
return ResultResponse.getFailResult(ResultCode.BODY_NOT_MATCH.getResultCode(), msg);
}
}特別的,get請求校驗:
@RestController
@RequestMapping(value = "/test")
@Slf4j
//@ApiIgnore
@Validated
public class TestController {
@GetMapping(value = "/test")
public Result<Object> test(@NotNull(message = "name必傳")
@NotBlank(message = "name格式錯誤")String name) {
return ResultResponse.getSuccessResult("hello: " + name);
}
}二、@Validated注解
1、@Validated 和 @Valid 區(qū)別
- @Validate 是對@Valid 的封裝
- @Validate 可以進(jìn)行分組驗證 ,一個對象中都寫了驗證而你只需要驗證該方法需要的驗證時使用
2、為何要分組校驗?
假設(shè)有這樣一種場景:
我們使用同一個VO(Request)類來傳遞save和update方法的數(shù)據(jù),但對于id來說,通常有框架幫我們生成id,我們不需要傳id此時需要使用注解@Null表名id數(shù)據(jù)必須為空。
但對于update方法,我們必須傳id才能進(jìn)行update操作,所以同一個字段面對不同的場景不同需求就可以使用分組校驗。
3、代碼實操
(1)創(chuàng)建分組接口
這里并不需要實現(xiàn)編寫什么代碼,標(biāo)明分類。
//分組接口 1
public interface InsertGroup {
}
//分組接口 2
public class UpdateGroup {
}(2)Request實體類
@Data
@NoArgsConstructor
public class TestRequest {
@Null(message = "無需傳id",groups = InsertGroup.class)
@NotBlank(message = "必須傳入id",groups = UpdateGroup.class)
private String id;
}(3)controller接口
@RequestMapping("/valid/test")
public ResponseEntity<ResponseResult> test(@Validated({UpdateGroup.class})@RequestBody TestRequest request) {
System.out.println(request);
return null;
}(4)postman測試

(5)注意事項?。。?/p>
當(dāng)我們在controller層指定分組后,屬性上沒有表名分組的校驗還執(zhí)行么?
下面的Request實體中,id進(jìn)行了分組,address沒有進(jìn)行分組:
@Data
@NoArgsConstructor
public class TestRequest {
@Null(message = "無需傳id",groups = InsertGroup.class)
@NotBlank(message = "必須傳入id",groups = UpdateGroup.class)
private String id;
@Length(max = 3, message = "address最大長度是3")
private String address;
}postman測試:
如下圖,校驗沒有成功!?。。≌f明一旦開啟了分組校驗,就必須把所有的校驗規(guī)則都指定組別,不然不生效。

三、自定義校驗注解
業(yè)務(wù)場景
假設(shè)我們有一個字段比如showStatus只能由0和1兩個取值,我們可以使用正則,也可以自定義注解校驗,這里我們展示如何使用自定義校驗.
如下圖所示:

自定義注解實現(xiàn)過程
(1)編寫自定義注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
// Target表示注解使用的范圍
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
// 獲取注解的時間,固定值
@Retention(RetentionPolicy.RUNTIME)
// 匹配的校驗器,我們稍后編寫,關(guān)聯(lián)注解和校驗器
@Constraint(validatedBy = {StatusValueValidator.class})
@Documented
public @interface StatusValue {
// 錯誤信息去哪找,通常我們使用配置文件,稍后編寫
String message() default "{com.cbj.db_work.valid.ListValue.message}";
// 支持分組校驗
Class<?>[] groups() default {};
// 自定義負(fù)載信息
Class<? extends Payload>[] payload() default {};
// 指定參數(shù),就是上圖中指定的可取值的范圍
int [] vals() default {};
}(2)自定義校驗器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
// 實現(xiàn)ConstraintValidator接口,泛型值:<自定義注解類,被校驗值的數(shù)據(jù)類型>
public class StatusValueValidator implements ConstraintValidator<StatusValue, Integer> {
// 整體思路,使用set在initialize獲得參數(shù)信息,在isValid方法中校驗,成功true,失敗false
Set<Integer> set = new HashSet<>();
// 初始化方法,可以得到詳細(xì)信息
@Override
public void initialize(StatusValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
/**
* 校驗是否匹配
* @param value 就是需要校驗的值
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(integer);
}
}(3)編寫配置文件

com.cbj.db_work.valid.ListValue.message=必須提交指定的值
在配置文件中可以指定匹配錯誤時顯示的信息,也可以message指定。

(4)postman測試

總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- 使用@Validated和@Valid 解決list校驗的問題
- Java中的三種校驗注解的使用(@Valid,@Validated和@PathVariable)
- spring @Validated 注解開發(fā)中使用group分組校驗的實現(xiàn)
- Java參數(shù)校驗@Validated、@Valid介紹及使用詳解
- SpringBoot參數(shù)校驗之@Validated的使用詳解
- 使用@Validated注解進(jìn)行校驗卻沒有效果的解決
- Spring 中@Validated 分組校驗的使用解析
- Spring利用@Validated注解實現(xiàn)參數(shù)校驗詳解
- JAVA校驗之@Valid和@Validated實踐指南
相關(guān)文章
利用數(shù)組實現(xiàn)棧(Java實現(xiàn))
這篇文章主要為大家詳細(xì)介紹了利用數(shù)組實現(xiàn)棧,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-09-09
解決Spring JPA 使用@transaction注解時產(chǎn)生CGLIB代理沖突問題
這篇文章主要介紹了解決Spring JPA 使用@transaction注解時產(chǎn)生CGLIB代理沖突問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Java下SpringBoot創(chuàng)建定時任務(wù)詳解
這篇文章主要介紹了Java下SpringBoot創(chuàng)建定時任務(wù)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
java使用定時器實現(xiàn)監(jiān)聽數(shù)據(jù)變化
這篇文章主要為大家詳細(xì)介紹了Java如何使用定時器監(jiān)聽數(shù)據(jù)變化,當(dāng)滿足某個條件時(例如沒有數(shù)據(jù)更新)自動執(zhí)行某項任務(wù),有興趣的可以了解下2023-11-11
如何開啟控制臺輸出mybatis執(zhí)行的sql日志問題
這篇文章主要介紹了如何開啟控制臺輸出mybatis執(zhí)行的sql日志問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09

