@Valid和@Validated注解校驗以及異常處理方式
前言
在Javaweb的開發(fā)中,為了防止懂技術(shù)的人對數(shù)據(jù)庫的惡意攻擊,我們通常使用參數(shù)校驗對無效數(shù)據(jù)進行篩選,Java生態(tài)下的@valid注解配置SpringBoot的使用可以方便快速的完成對數(shù)據(jù)校驗的各種場景。
同時數(shù)據(jù)校驗分為前端校驗和后端校驗。
可為何前端做完校驗之后,還要在后端進行校驗?
如果有人拿到了url地址,使用第三方測試工具比如postman就可以跳過前端頁面的參數(shù)檢驗,所以為了數(shù)據(jù)庫數(shù)據(jù)正確性,我們十分有必要對傳來的數(shù)據(jù)在后端進行第二次校驗
一、@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)空校驗
注解 | 應用 |
---|---|
@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校驗
注解 | 應用 |
---|---|
@AssertFalse | 限制必須為false |
@AssertTrue | 限制必須為true |
(3)長度校驗
注解 | 應用 |
---|---|
@Size(max,min) | 驗證對象(Array,Collection,Map,String)長度是否在給定的范圍之內(nèi) |
@Length(min=, max=) | 驗證字符串長度是否在給定的范圍之內(nèi) |
(4)日期校驗
注解 | 應用 |
---|---|
@Past | 限制必須是一個過去的日期,并且類型為java.util.Date |
@Future | 限制必須是一個將來的日期,并且類型為java.util.Date |
@Pattern(value) | 限制必須符合指定的正則表達式 |
(5)數(shù)值校驗
建議使用在Stirng,Integer類型,不建議使用在int類型上,因為表單值為“”時無法轉(zhuǎn)換為int,但可以轉(zhuǎn)換為Stirng為"",Integer為null。
注解 | 應用 |
---|---|
@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)其他校驗
注解 | 應用 |
---|---|
驗證注解的元素值是Email,也可以通過正則表達式和flag指定自定義的email |
4、具體使用
使用 @Valid 進行參數(shù)效驗步驟:
- 實體類中添加 @Valid 相關(guān)注解
- 接口類中添加 @Valid 注解
- 全局異常處理類中處理 @Valid 拋出的異常
運行流程:
整個過程如下圖所示,用戶訪問接口,然后進行參數(shù)效驗,因為 @Valid 不支持平面的參數(shù)效驗(直接寫在參數(shù)中字段的效驗)所以基于 GET 請求的參數(shù)還是按照原先方式進行效驗,而 POST 則可以以實體對象為參數(shù),可以使用 @Valid 方式進行效驗。
如果效驗通過,則進入業(yè)務邏輯,否則拋出異常,交由全局異常處理器進行處理。
代碼實踐:
(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è)務中,有時候需要處理這個異常,這個時候就需要一個全局異常處理類中處理 @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) //設置狀態(tài)碼為 400 @ExceptionHandler({MethodArgumentNotValidException.class}) public String paramExceptionHandler(MethodArgumentNotValidException e) { BindingResult exceptions = e.getBindingResult(); // 判斷異常中是否有錯誤信息,如果存在就使用異常中的消息,否則使用默認消息 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)在這:
原因:
本應當(只能)使用無參構(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 可以進行分組驗證 ,一個對象中都寫了驗證而你只需要驗證該方法需要的驗證時使用
2、為何要分組校驗?
假設有這樣一種場景:
我們使用同一個VO(Request)類來傳遞save和update方法的數(shù)據(jù),但對于id來說,通常有框架幫我們生成id,我們不需要傳id此時需要使用注解@Null表名id數(shù)據(jù)必須為空。
但對于update方法,我們必須傳id才能進行update操作,所以同一個字段面對不同的場景不同需求就可以使用分組校驗。
3、代碼實操
(1)創(chuàng)建分組接口
這里并不需要實現(xiàn)編寫什么代碼,標明分類。
//分組接口 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>
當我們在controller層指定分組后,屬性上沒有表名分組的校驗還執(zhí)行么?
下面的Request實體中,id進行了分組,address沒有進行分組:
@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測試:
如下圖,校驗沒有成功?。。?!說明一旦開啟了分組校驗,就必須把所有的校驗規(guī)則都指定組別,不然不生效。
三、自定義校驗注解
業(yè)務場景
假設我們有一個字段比如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 {}; // 自定義負載信息 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<>(); // 初始化方法,可以得到詳細信息 @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)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
IDEA?Error:java:無效的源發(fā)行版:13的解決過程
之前用idea運行時,也會出現(xiàn)這種情況,后面通過網(wǎng)上的資料解決了這個問題,下面這篇文章主要給大家介紹了關(guān)于IDEA?Error:java:無效的源發(fā)行版:13的解決過程,需要的朋友可以參考下2023-01-01Java實戰(zhàn)之基于TCP實現(xiàn)簡單聊天程序
這篇文章主要為大家詳細介紹了如何在Java中基于TCP實現(xiàn)簡單聊天程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03Springboot整合easyexcel實現(xiàn)一個接口任意表的Excel導入導出
本文主要介紹了Springboot整合easyexcel實現(xiàn)一個接口任意表的Excel導入導出,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-02-02Java System類詳解_動力節(jié)點Java學院整理
System類是jdk提供的一個工具類,有final修飾,不可繼承,由名字可以看出來,其中的操作多數(shù)和系統(tǒng)相關(guān)。這篇文章主要介紹了Java System類詳解_動力節(jié)點Java學院整理,需要的朋友可以參考下2017-04-04