@valid校验_SpringBoot数据校验与优雅处理详解
本篇要点
JDK1.8、SpringBoot2.3.4release
说明后端参数校验的必要性。
介绍如何使⽤validator进⾏参数校验
如何使⽤validator进⾏参数校验。
介绍@Valid和@Validated的区别。
如何⾃定义约束注解。
介绍如何⾃定义约束注解
关于Bean Validation的前世今⽣
后端参数校验的必要性
在开发中,从表现层到持久化层,数据校验都是⼀项逻辑差不多,但容易出错的任务,
前端框架往往会采取⼀些检查参数的⼿段,⽐如校验并提⽰信息,那么,既然前端已经存在校验⼿段,后端的校验是否还有必要,是否多余了呢?
如果后端不做校验,⼀旦通过特殊⼿段越过前端的检测,系统就会出现安并不是,正常情况下,参数确实会经过前端校验传向后端,但如果后端不做校验,⼀旦通过特殊⼿段越过前端的检测,系统就会出现安全漏洞。
不使⽤Validator的参数处理逻辑
既然是参数校验,很简单呀,⽤⼏个if/else直接搞定:
@PostMapping("/form")
public String form(@RequestBody Person person) {
if (Name() == null) {
return "姓名不能为null";
}
if (Name().length() < 6 || Name().length() > 12) {
return "姓名长度必须在6 - 12之间";
}
if (Age() == null) {
return "年龄不能为null";
}
if (Age() < 20) {
return "年龄最⼩需要20";
}
// service ..
return "注册成功!";
}
写法⼲脆,但if/else太多,过于臃肿,更何况这只是区区⼀个接⼝的两个参数⽽已,要是需要更多参数校验,甚⾄更多⽅法都需要这要的校验,这代码量可想⽽知。于是,这种做法显然是不可取的,我们可以利⽤下⾯这种更加优雅的参数处理⽅式。
Validator框架提供的便利
Validating data
Validating data is a common task that occurs throughout all application layers, from the presentation to the
Often the same validation logic is implemented in each layer which is time consuming and error-persistence layer. Often the same validation
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定义了⼀个元数据模型,为实体和⽅法提供了数据验证的API,默认将注解作为源,可以通过XML扩展源。
SpringBoot⾃动配置ValidationAutoConfiguration
Hibernate Validator是Jakarta Bean Validation的参考实现。
在SpringBoot中,只要类路径上存在JSR-303的实现,如Hibernate Validator,就会⾃动开启Bean Validation验证功能,这⾥我们只要引⼊spring-boot-starter-validation的依赖,就能完成所需。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
⽬的其实是为了引⼊如下依赖:
<!-- Unified EL 获取动态表达式-->
<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() {
//ValidatorFactory
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.Object());
return factoryBean;
}
// ⽀持Aop,MethodValidationInterceptor⽅法级别的
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
@Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = Property("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
// Validator(); 通过factoryBean获取了Validator实例,并设置
processor.setValidator(validator);
return processor;
}
}
Validator+BindingResult优雅处理
默认已经引⼊相关依赖。
为实体类定义约束注解
/**
* 实体类字段加上straints定义的注解
* @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层做⽅法参数校验时功能相近,具体区别可以往后⾯看。
@RestController
public class ValidateController {
@PostMapping("/person")
public Map<String, Object> validatePerson(@Validated @RequestBody Person person, BindingResult result) { Map<String, Object> map = new HashMap<>();
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult⾥
if (result.hasErrors()) {
List<String> res = new ArrayList<>();
String field = Field();
Object value = RejectedValue();
String msg = DefaultMessage();
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;
}
}
发送Post请求,伪造不合法数据
这⾥使⽤IDEA提供的HTTP Client⼯具发送请求。
POST 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 原因 -> 个数必须在6和12之间",
"错误字段 -> age 错误值 -> 10 原因 -> 最⼩不能⼩于20"
]
}
Response code: 200; Time: 393ms; Content length: 92 bytes
Validator + 全局异常处理
在接⼝⽅法中利⽤BindingResult处理校验数据过程中的信息是⼀个可⾏⽅案,但在接⼝众多的情况下,就显得有些冗余,我们可以利⽤全局异常处理,捕捉抛出的MethodArgumentNotValidException异常,并进⾏相应的处理。
定义全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* If the bean validation is failed, it will trigger a MethodArgumentNotValidException.
springboot aop*/
@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<>();
String field = Field();
Object value = RejectedValue();
String msg = DefaultMessage();
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;
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论