日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

@valid 校验_SpringBoot数据校验与优雅处理详解

發(fā)布時間:2023/12/19 javascript 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 @valid 校验_SpringBoot数据校验与优雅处理详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本篇要點

JDK1.8、SpringBoot2.3.4release
  • 說明后端參數(shù)校驗的必要性。
  • 介紹如何使用validator進行參數(shù)校驗。
  • 介紹@Valid和@Validated的區(qū)別。
  • 介紹如何自定義約束注解。
  • 關于Bean Validation的前世今生

后端參數(shù)校驗的必要性

在開發(fā)中,從表現(xiàn)層到持久化層,數(shù)據校驗都是一項邏輯差不多,但容易出錯的任務,

前端框架往往會采取一些檢查參數(shù)的手段,比如校驗并提示信息,那么,既然前端已經存在校驗手段,后端的校驗是否還有必要,是否多余了呢?

并不是,正常情況下,參數(shù)確實會經過前端校驗傳向后端,但如果后端不做校驗,一旦通過特殊手段越過前端的檢測,系統(tǒng)就會出現(xiàn)安全漏洞。

不使用Validator的參數(shù)處理邏輯

既然是參數(shù)校驗,很簡單呀,用幾個if/else直接搞定:

@PostMapping("/form")public String form(@RequestBody Person person) {if (person.getName() == null) {return "姓名不能為null";}if (person.getName().length() < 6 || person.getName().length() > 12) {return "姓名長度必須在6 - 12之間";}if (person.getAge() == null) {return "年齡不能為null";}if (person.getAge() < 20) {return "年齡最小需要20";}// service ..return "注冊成功!";}

寫法干脆,但if/else太多,過于臃腫,更何況這只是區(qū)區(qū)一個接口的兩個參數(shù)而已,要是需要更多參數(shù)校驗,甚至更多方法都需要這要的校驗,這代碼量可想而知。于是,這種做法顯然是不可取的,我們可以利用下面這種更加優(yōu)雅的參數(shù)處理方式。

Validator框架提供的便利

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone.

如果依照下圖的架構,對每個層級都進行類似的校驗,未免過于冗雜。

Jakarta Bean Validation 2.0 - defines a metadata model and API for entity and method validation. The default metadata source are annotations, with the ability to override and extend the meta-data through the use of XML.
The API is not tied to a specific application tier nor programming model. It is specifically not tied to either web or persistence tier, and is available for both server-side application programming, as well as rich client Swing application developers.

Jakarta Bean Validation2.0定義了一個元數(shù)據模型,為實體和方法提供了數(shù)據驗證的API,默認將注解作為源,可以通過XML擴展源。

SpringBoot自動配置ValidationAutoConfiguration

Hibernate Validator是Jakarta Bean Validation的參考實現(xiàn)。

在SpringBoot中,只要類路徑上存在JSR-303的實現(xiàn),如Hibernate Validator,就會自動開啟Bean Validation驗證功能,這里我們只要引入spring-boot-starter-validation的依賴,就能完成所需。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

目的其實是為了引入如下依賴:

<!-- Unified EL 獲取動態(tài)表達式--><dependency><groupId>org.glassfish</groupId><artifactId>jakarta.el</artifactId><version>3.0.3</version><scope>compile</scope></dependency><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.1.5.Final</version><scope>compile</scope></dependency>

SpringBoot對BeanValidation的支持的自動裝配定義在org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration類中,提供了默認的LocalValidatorFactoryBean和支持方法級別的攔截器MethodValidationPostProcessor。

@Configuration(proxyBeanMethods = false) @ConditionalOnClass(ExecutableValidator.class) @ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider") @Import(PrimaryDefaultValidatorPostProcessor.class) public class ValidationAutoConfiguration {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)@ConditionalOnMissingBean(Validator.class)public static LocalValidatorFactoryBean defaultValidator() {//ValidatorFactoryLocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();factoryBean.setMessageInterpolator(interpolatorFactory.getObject());return factoryBean;}// 支持Aop,MethodValidationInterceptor方法級別的攔截器@Bean@ConditionalOnMissingBeanpublic static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,@Lazy Validator validator) {MethodValidationPostProcessor processor = new MethodValidationPostProcessor();boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);processor.setProxyTargetClass(proxyTargetClass);// factory.getValidator(); 通過factoryBean獲取了Validator實例,并設置processor.setValidator(validator);return processor;}}

Validator+BindingResult優(yōu)雅處理

默認已經引入相關依賴。

為實體類定義約束注解

/*** 實體類字段加上javax.validation.constraints定義的注解* @author Summerday*/@Data @ToString public class Person {private Integer id;@NotNull@Size(min = 6,max = 12)private String name;@NotNull@Min(20)private Integer age; }

使用@Valid或@Validated注解

@Valid和@Validated在Controller層做方法參數(shù)校驗時功能相近,具體區(qū)別可以往后面看。

@RestController public class ValidateController {@PostMapping("/person")public Map<String, Object> validatePerson(@Validated @RequestBody Person person, BindingResult result) {Map<String, Object> map = new HashMap<>();// 如果有參數(shù)校驗失敗,會將錯誤信息封裝成對象組裝在BindingResult里if (result.hasErrors()) {List<String> res = new ArrayList<>();result.getFieldErrors().forEach(error -> {String field = error.getField();Object value = error.getRejectedValue();String msg = error.getDefaultMessage();res.add(String.format("錯誤字段 -> %s 錯誤值 -> %s 原因 -> %s", field, value, msg));});map.put("msg", res);return map;}map.put("msg", "success");System.out.println(person);return map;} }

發(fā)送Post請求,偽造不合法數(shù)據

這里使用IDEA提供的HTTP Client工具發(fā)送請求。

POST http://localhost:8081/person Content-Type: application/json{"name": "hyh","age": 10 }

響應信息如下:

HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Sat, 14 Nov 2020 15:58:17 GMT Keep-Alive: timeout=60 Connection: keep-alive{"msg": ["錯誤字段 -> name 錯誤值 -> hyh 原因 -> 個數(shù)必須在6和12之間","錯誤字段 -> age 錯誤值 -> 10 原因 -> 最小不能小于20"] }Response code: 200; Time: 393ms; Content length: 92 bytes

Validator + 全局異常處理

在接口方法中利用BindingResult處理校驗數(shù)據過程中的信息是一個可行方案,但在接口眾多的情況下,就顯得有些冗余,我們可以利用全局異常處理,捕捉拋出的MethodArgumentNotValidException異常,并進行相應的處理。

定義全局異常處理

@RestControllerAdvice public class GlobalExceptionHandler {/*** If the bean validation is failed, it will trigger a MethodArgumentNotValidException.*/@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpStatus status) {BindingResult result = ex.getBindingResult();Map<String, Object> map = new HashMap<>();List<String> list = new LinkedList<>();result.getFieldErrors().forEach(error -> {String field = error.getField();Object value = error.getRejectedValue();String msg = error.getDefaultMessage();list.add(String.format("錯誤字段 -> %s 錯誤值 -> %s 原因 -> %s", field, value, msg));});map.put("msg", list);return new ResponseEntity<>(map, status);} }

定義接口

@RestController public class ValidateController {@PostMapping("/person")public Map<String, Object> validatePerson(@Valid @RequestBody Person person) {Map<String, Object> map = new HashMap<>();map.put("msg", "success");System.out.println(person);return map;} }

@Validated精確校驗到參數(shù)字段

有時候,我們只想校驗某個參數(shù)字段,并不想校驗整個pojo對象,我們可以利用@Validated精確校驗到某個字段。

定義接口

@RestController @Validated public class OnlyParamsController {@GetMapping("/{id}/{name}")public String test(@PathVariable("id") @Min(1) Long id,@PathVariable("name") @Size(min = 5, max = 10) String name) {return "success";} }

發(fā)送GET請求,偽造不合法信息

GET http://localhost:8081/0/hyh Content-Type: application/json

未作任何處理,響應結果如下:

{"timestamp": "2020-11-15T15:23:29.734+00:00","status": 500,"error": "Internal Server Error","trace": "javax.validation.ConstraintViolationException: test.id: 最小不能小于1, test.name: 個數(shù)必須在5和10之間...省略","message": "test.id: 最小不能小于1, test.name: 個數(shù)必須在5和10之間","path": "/0/hyh" }

可以看到,校驗已經生效,但狀態(tài)和響應錯誤信息不太正確,我們可以通過捕獲ConstraintViolationException修改狀態(tài)。

捕獲異常,處理結果

@ControllerAdvice public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {private static final Logger log = LoggerFactory.getLogger(CustomGlobalExceptionHandler.class);/*** If the @Validated is failed, it will trigger a ConstraintViolationException*/@ExceptionHandler(ConstraintViolationException.class)public void constraintViolationException(ConstraintViolationException ex, HttpServletResponse response) throws IOException {ex.getConstraintViolations().forEach(x -> {String message = x.getMessage();Path propertyPath = x.getPropertyPath();Object invalidValue = x.getInvalidValue();log.error("錯誤字段 -> {} 錯誤值 -> {} 原因 -> {}", propertyPath, invalidValue, message);});response.sendError(HttpStatus.BAD_REQUEST.value());} }

@Validated和@Valid的不同

參考:@Validated和@Valid的區(qū)別?教你使用它完成Controller參數(shù)校驗(含級聯(lián)屬性校驗)以及原理分析【享學Spring】

  • @Valid是標準JSR-303規(guī)范的標記型注解,用來標記驗證屬性和方法返回值,進行級聯(lián)和遞歸校驗。
  • @Validated:是Spring提供的注解,是標準JSR-303的一個變種(補充),提供了一個分組功能,可以在入參驗證時,根據不同的分組采用不同的驗證機制。
  • 在Controller中校驗方法參數(shù)時,使用@Valid和@Validated并無特殊差異(若不需要分組校驗的話)。
  • @Validated注解可以用于類級別,用于支持Spring進行方法級別的參數(shù)校驗。@Valid可以用在屬性級別約束,用來表示級聯(lián)校驗。
  • @Validated只能用在類、方法和參數(shù)上,而@Valid可用于方法、字段、構造器和參數(shù)上。

如何自定義注解

Jakarta Bean Validation API定義了一套標準約束注解,如@NotNull,@Size等,但是這些內置的約束注解難免會不能滿足我們的需求,這時我們就可以自定義注解,創(chuàng)建自定義注解需要三步:

  • 創(chuàng)建一個constraint annotation。
  • 實現(xiàn)一個validator。
  • 定義一個default error message。
  • 創(chuàng)建一個constraint annotation

    /*** 自定義注解* @author Summerday*/@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}) @Retention(RUNTIME) @Constraint(validatedBy = CheckCaseValidator.class) //需要定義CheckCaseValidator @Documented @Repeatable(CheckCase.List.class) public @interface CheckCase {String message() default "{CheckCase.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};CaseMode value();@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})@Retention(RUNTIME)@Documented@interface List {CheckCase[] value();} }

    實現(xiàn)一個validator

    /*** 實現(xiàn)ConstraintValidator** @author Summerday*/ public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {private CaseMode caseMode;/*** 初始化獲取注解中的值*/@Overridepublic void initialize(CheckCase constraintAnnotation) {this.caseMode = constraintAnnotation.value();}/*** 校驗*/@Overridepublic boolean isValid(String object, ConstraintValidatorContext constraintContext) {if (object == null) {return true;}boolean isValid;if (caseMode == CaseMode.UPPER) {isValid = object.equals(object.toUpperCase());} else {isValid = object.equals(object.toLowerCase());}if (!isValid) {// 如果定義了message值,就用定義的,沒有則去// ValidationMessages.properties中找CheckCase.message的值if(constraintContext.getDefaultConstraintMessageTemplate().isEmpty()){constraintContext.disableDefaultConstraintViolation();constraintContext.buildConstraintViolationWithTemplate("{CheckCase.message}").addConstraintViolation();}}return isValid;} }

    定義一個default error message

    在ValidationMessages.properties文件中定義:

    CheckCase.message=Case mode must be {value}.

    這樣,自定義的注解就完成了,如果感興趣可以自行測試一下,在某個字段上加上注解:@CheckCase(value = CaseMode.UPPER)。

    源碼下載

    總結

    以上是生活随笔為你收集整理的@valid 校验_SpringBoot数据校验与优雅处理详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。