Spring Boot之輸入Bean驗證@Valid應用場景總結
Validation應用場景
在日常應用中,存在大量需要針對輸入資料進行驗證的應用場景,例如字串的大小,ip地址驗證,email格式驗證等等各類情況。
值得開心的是Spring Boot將這類場景進行了整合抽象,提供一個完整而且靈活的機制簡化開發者在進行相關操作時的重複工作量。
Maven引用
在pom檔案中新增如下資訊:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
就可以將相關Validation操作引入系統了。
基於@Valid驗證JsonBean的輸入實體Bean
定義Product實體類:
public class Product {
//限購:1~5
@Min(value = 1, message = "min Value is above zero")
@Max(value = 10, message = "最大值不超過10")
private int count;
@Pattern(regexp = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", message = "不符合IP規則")
private String ipAddr;
@Email(message = "email format error")
@NotBlank
private String email;
@NotBlank(message = "name 不能為空")
@Length(min=5, max = 10, message = "name長度在5到10個字元之間")
private String name;
}
Controller測試類:
@RestController
@Slf4j
public class TestController {
@PostMapping("/test")
public ResponseEntity<String> validateJsonBean(@Valid @RequestBody Product product) {
return ResponseEntity.ok("valid id done");
}
}
輸入測試資料:
{
"name": "asd",
"count":100,
"ipAddr": "12.2.1",
"email":""
}
其相應結果資訊如下:
{
"timestamp": "2019-03-25T11:29:43.382+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Min.product.count",
"Min.count",
"Min"
],
"arguments": [
{
"codes": [
"product.count",
"count"
],
"arguments": null,
"defaultMessage": "count",
"code": "count"
},
1
],
"defaultMessage": "min Value is above zero",
"objectName": "product",
"field": "count",
"rejectedValue": 0,
"bindingFailure": false,
"code": "Min"
},
{
"codes": [
"NotBlank.product.email",
"NotBlank.email",
"NotBlank"
],
"arguments": [
{
"codes": [
"product.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
}
],
"defaultMessage": "不能為空",
"objectName": "product",
"field": "email",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
},
{
"codes": [
"NotBlank.product.name",
"NotBlank.name",
"NotBlank"
],
"arguments": [
{
"codes": [
"product.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "name 不能為空",
"objectName": "product",
"field": "name",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='product'. Error count: 3",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<java.lang.String> org.cjf.validationdemo.controller.TestController.validateJsonBean(org.cjf.validationdemo.beans.Product) with 3 errors: [Field error in object 'product' on field 'count': rejected value [0]; codes [Min.product.count,Min.count,Min]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [product.count,count]; arguments []; default message [count],1]; default message [min Value is above zero]] [Field error in object 'product' on field 'email': rejected value [null]; codes [NotBlank.product.email,NotBlank.email,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [product.email,email]; arguments []; default message [email]]; default message [不能為空]] [Field error in object 'product' on field 'name': rejected value [null]; codes [NotBlank.product.name,NotBlank.name,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [product.name,name]; arguments []; default message [name]]; default message [name 不能為空]] \n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:138)\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:166)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.lang.Thread.run(Thread.java:748)\n",
"path": "/test"
}
從上述資訊中可以發現,其status code為400,表示特定錯誤資訊。在程式碼實現層面,其將丟擲MethodArgumentNotValidException的異常,由Spring框架進行捕獲,並轉化為400的異常資訊提示出來。
在errors中展示了定製的錯誤提示資訊. 這裡沒有定製錯誤的提示結果,使用了預設的error提示資訊結果格式。
如果定製化錯誤資訊的展示結果,會更新人性化一些,可讀性會更好。具體示例如下:
@Data
public class Product {
//限購:1~5
@Min(value = 1, message = "min Value is above zero")
@Max(value = 10, message = "最大值不超過10")
private int count;
@Pattern(regexp = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", message = "不符合IP規則")
private String ipAddr;
@Email(message = "email format error")
@NotBlank
private String email;
@NotBlank(message = "name 不能為空")
@Length(min=5, max = 10, message = "name長度在5到10個字元之間")
private String name;
}
基於@RequestParam和@PathVariable增加Valid
在Spring MVC應用中,允許使用上述各類的valid註解去驗證@RequestParam和@PathVariable中定義的各類params。
具體示例如下:
@Controller
@Validated
public class PathVariableController {
@GetMapping("/valid/test/{id}")
public ResponseEntity<String> handleTest(@PathVariable("id") @Max(10) int id,
@RequestParam("count") @Min(5) int count) {
return ResponseEntity.ok("validation is done");
}
}
注意這裡使用@Validated來修飾整個類,表示其中方法需要在method這個級別使用validation邏輯進行驗證。
針對@PathVariable/@RequestParam直接掛接註解的方式來修飾特定變數。
注意一下,這裡@Validated驗證丟擲的異常是javax.validation.ConstraintViolationException,在瀏覽器訪問的過程中,其體會提示500的內部錯誤資訊。
如果希望定製這個錯誤資訊,可以將這個異常進行捕獲,然後自行定義響應資訊,具體做法如下:
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* Exception Handler.
*
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>("be valid due to validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
}
這裡ControllerAdvice是表示其為advice,用來接收controller的相關處理邏輯。
@ExceptionHandler主要用來定義系統內部發生的特定異常的處理邏輯。
在這裡是將異常轉化為了400的status code資訊。
單元測試程式碼如下:
@RunWith(SpringRunner.class)
@WebMvcTest(controllers={PathVariableController.class})
@Slf4j
public class PathVariableTestTest {
@Autowired
private MockMvc mockMvc;
@Before
public void before() throws Exception {
}
@After
public void after() throws Exception {
}
@Test
public void testValidParams() throws Exception {
mockMvc.perform(get("/valid/test/{id}?count={count}", 9, 6))
.andExpect(status().isOk());
}
@Test
public void testInValidParams() throws Exception {
mockMvc.perform(get("/valid/test/{id}?count={count}", 123, 4))
.andExpect(status().isBadRequest());
}
}
在這個測試用例中,主要是基於Http Status Code來進行判斷計算結果的正確性。
小結
在本節中,主要測試了基於@Valid和@Validated兩種模式下的驗證邏輯,它們應用在不同場合,會丟擲不同的異常資訊,分別為:MethodArgumentNotValidException和javax.validation.ConstraintViolationException,前一個有Spring框架將其轉化為400的Bad Request,而後者則直接定位為500的內部錯誤。
對於後者的異常資訊,這裡定義了異常的捕獲邏輯,自行將其轉為一個400的Bad Request。也希望大家可以從中學習如何來使用自定義的異常處理邏輯。
相關文章
- Spring Boot之驗證模組應用總結2Spring Boot
- Spring Boot中實現輸入引數驗證教程Spring Boot
- Spring Boot之單元測試用例總結Spring Boot
- 使用 JWT 身份驗證保護你的 Spring Boot 應用JWTSpring Boot
- Redis 應用場景彙總Redis
- spring boot factory beanSpring BootBean
- Tomcat高階配置(應用場景總結及示例)Tomcat
- Laravel 驗證類 實現 路由場景驗證 和 控制器場景驗證Laravel路由
- 解析如何進行Laravel表單驗證分層設計和驗證場景應用Laravel
- 關於Laravel的表單驗證分層設計以及驗證場景的應用Laravel
- Spring Boot之Validation自定義實現總結Spring Boot
- 《前端實戰總結》之迭代器模式的N+1種應用場景前端模式
- 2018VR市場總結:行業重新洗牌,應用場景增加VR行業
- Redis系列之(二)——應用場景Redis
- 登入驗證碼生成kaptcha(輸入驗證碼)APT
- lapis的輸入驗證API
- 輸入表單驗證
- 資料結構之Redis應用~常用命令~應用場景(重點)資料結構Redis
- MQMQ的快速入門+應用場景MQ
- Spring Boot 配置檔案總結Spring Boot
- Spring Boot + JPA學習總結Spring Boot
- Zookeeper應用場景彙總(超詳細)
- Spring Boot使用JWT進行token驗證Spring BootJWT
- 總結一些開發語言對應的技術應用場景
- Android View篇之自定義驗證碼輸入框AndroidView
- Spring Boot 啟動原始碼解析結合Spring Bean生命週期分析Spring Boot原始碼Bean
- Flutter 驗證碼輸入框Flutter
- Redis set資料型別命令使用及應用場景使用總結Redis資料型別
- Spring Boot + Security + JWT 實現Token驗證+多Provider——登入系統Spring BootJWTIDE
- 為你的 Laravel 驗證器加上多驗證場景Laravel
- lumen驗證類 實現控制器場景驗證
- Spring原始碼之Bean的載入(二)Spring原始碼Bean
- Spring原始碼之Bean的載入(四)Spring原始碼Bean
- Spring原始碼之Bean的載入(三)Spring原始碼Bean
- Spring原始碼之Bean的載入(一)Spring原始碼Bean
- linux實用命令以及實用場景總結Linux
- Flutter 密碼輸入框 驗證碼輸入框Flutter密碼
- ios runtime之Method Swizzling及其應用場景iOS