简介
@Validation 是一套帮助我们继续对传输的参数进行数据校验的注解 ,通过配置Validation可以很轻松的完成对数据的约束,配合BindingResult 可以直接提供参数验证结果
所有参数注解含义
参考 @Validated注解详解,分组校验,嵌套校验,@Valid和@Validated 区别,Spring Boot @Validated
区别
javax.validation.Valid.@Valid
① 首先需要在实体类的相应字段上添加用于充当校验条件的注解,如:@Min,如下代码(age属于Girl类中的属性):
@Min(value = 18,message = "未成年禁止入内")
private Integer age;
② 其次在controller层的方法的要校验的参数上添加@Valid注解,并且需要传入BindingResult对象,用于获取校验失败情况下的反馈信息,如下代码:
@PostMapping("/girls")
public Girl addGirl(@Valid Girl girl, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
System.out.println(bindingResult.getFieldError().getDefaultMessage());
return null;
}
return girlResposity.save(girl);
}
bindingResult.getFieldError.getDefaultMessage() 用于获取相应字段上添加的message中的内容 ,如:@Min 注解中message属性的内容
javax.validation.@Validated
@Validated是@Valid 的一次封装 ,是Spring提供的校验机制使用。@Valid不提供分组功能
分组
当一个实体类需要多种验证方式时,例:对于一个实体类的id来说,新增的时候是不需要的,对于更新时是必须的。 可以通过groups对验证进行分组
package com.valid.pojo;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
import com.valid.interfaces.First;
public class People {
@NotEmpty(groups={First.class})
private String id;
@NotEmpty
@Size(min=3,max=8)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public interface First {}
}
(1)不分配groups,默认每次都要进行验证
(2)对一个参数需要多种验证方式时,也可通过分配不同的组达到目的。例:
@NotEmpty(groups={First.class})
@Size(min=3,max=8,groups={Second.class})
private String name;
@Controller
public class FirstController {
@RequestMapping("/addPeople")
public @ResponseBody String addPeople(@Validated People p,BindingResult result) {
...
}
@RequestMapping("/updatePeople")
public @ResponseBody String updatePeople(@Validated({First.class}) People p,BindingResult result) {
...
}
}
@Validated没有添加groups属性 时,默认验证没有分组的验证属性 ,如该例子:People的name 属性。如果所有参数的验证类型都设置了分组 (即本例中People的name的@NotEmpty 、@Size 都添加groups属性),则不验证任何参数
注意
controller 接口中使用 @Validated 时
@Validated(groups = A.class) 设置了 groups 属性,则只有 配置了 groups = A.class 的效验注解会生效,没有配置的全不生效
@Validated 没有设置 groups 属性,则所有效验注解都会生效
controller 中,不使用@Valid注解 ,而是要使用@Validated ,里面value代表的是,在User类里面@NotNull 注解里面配置了groups 里面有TestNotNull.class 的字段判断会生效 那么当前配置 的话,就只会判断username是否为空 ,而password因为没有配置同样的groups属性 ,所以不会生效
@GetMapping("test")
public Result test(@Validated(value = {TestNotNull.class}) User user) {
System.out.println("测试@notNull注解");
return Result.suc();
}
这里没有传password ,代码没有抛异常,说明@Validated注解不会判断groups属性没有当前class的注解 ,再试一下不传username会不会抛异常 这里没有传username ,返回的是用户名不能为空,说明配置成功了,如果以后开发中,在多个接口中有不同的判断体系,可以用groups的方式分组
验证多个对象
一个功能方法上处理多个模型对象 时,需添加多个验证结果对象
@Controller
public class FirstController {
@RequestMapping("/addPeople")
public @ResponseBody String addPeople(@Validated People p,BindingResult result,@Validated Person p2,BindingResult result2) {
...
}
}
总结
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
Class<?>[] value() default {};
}
@Valid:没有分组的功能。
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
嵌套验证
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@NotNull(message = "props不能为空")
@Size(min = 1, message = "至少要有一个属性")
private List<Prop> props;
}
Item带有很多属性,属性Prop 里面有属性id,属性值vid,属性名和属性值,如下所示:
public class Prop {
@NotNull(message = "pid不能为空")
@Min(value = 1, message = "pid必须为正整数")
private Long pid;
@NotNull(message = "vid不能为空")
@Min(value = 1, message = "vid必须为正整数")
private Long vid;
@NotBlank(message = "pidName不能为空")
private String pidName;
@NotBlank(message = "vidName不能为空")
private String vidName;
}
属性Prop这个实体也有自己的验证机制 ,比如属性和属性值vid不能为空,属性名和属性值不能为空 等。 现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示:
@RestController
public class ItemController {
@RequestMapping("/item/add")
public void addItem(@Validated Item item, BindingResult bindingResult) {
doSomething();
}
}
如果Item实体的props属性不额外加注释 ,只有@NotNull和@Size ,无论入参 采用@Validated 还是@Valid 验证,Spring Validation 框架只会对Item的id和props做非空和数量验证 ,不会对props字段里的Prop实体进行字段验证
也就是@Validated 和@Valid加在方法参数前 ,都不会自动对参数进行嵌套验证
也就是说如果传的List<Prop>中有Prop的pid为空或者是负数,入参验证不会检测出来。
为了能够进行嵌套验证,必须手动 在Item实体的props字段上 明确指出这个字段里面的实体也要进行验证 。
所以
request 中的参数 尽量不要是其他的 dto , 可以是 enum ,如果必须用其他 dto,可以作为 静态内部类
由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上 ,而且@Valid 类注解上也说明了它支持嵌套验证功能
那么我们能够推断出:
@Valid加在方法参数时并不能够自动进行嵌套验证
而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@Valid
@NotNull(message = "props不能为空")
@Size(min = 1, message = "props至少要有一个自定义属性")
private List<Prop> props;
}
然后我们在ItemController 的addItem函数 上再使用@Validated或者@Valid ,就能对Item的入参进行嵌套验证 。此时Item 里面的props 如果含有Prop的相应字段为空 的情况,Spring Validation框架就会检测出来 ,bindingResult就会记录相应的错误 。
好的实现方式
对 List<包装类> 中包装类的效验
@Data
@ApiModel
public class xxx {
@ApiModelProperty("xxx")
@Size(max = 100)
@NotEmpty
private List<@NotBlank String> xxx;
}
package com.example.apps.advice;
import com.example.apps.result.ServiceResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
@RestControllerAdvice
public class GlobalControllerAdvice {
@ExceptionHandler(value = ConstraintViolationException.class)
public ServiceResult errorHandler(ConstraintViolationException ex) {
ServiceResult serviceResult = new ServiceResult(400);
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (!CollectionUtils.isEmpty(constraintViolations)) {
StringBuilder stringBuilder = new StringBuilder();
for (ConstraintViolation constraintViolation : constraintViolations) {
stringBuilder.append(constraintViolation.getMessage()).append(",");
}
String errorMessage = stringBuilder.toString();
if (errorMessage.length() > 1) {
errorMessage = StringUtils.removeEnd(errorMessage, ",");
serviceResult.setMessage(errorMessage);
return serviceResult;
}
}
serviceResult.setMessage(ex.getMessage());
return serviceResult;
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ServiceResult errorHandler(MethodArgumentNotValidException ex) {
ServiceResult serviceResult = new ServiceResult(400);
List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors();
if(!CollectionUtils.isEmpty(objectErrors)) {
StringBuilder builder = new StringBuilder();
for (ObjectError objectError : objectErrors) {
builder.append(objectError.getDefaultMessage()).append(",");
}
String errorMessage = builder.toString();
if (errorMessage.length() > 1) {
errorMessage = StringUtils.removeEnd(errorMessage,",");
}
serviceResult.setMessage(errorMessage);
return serviceResult;
}
serviceResult.setMessage(ex.getMessage());
return serviceResult;
}
}
对Controller里的方法的多个参数进行校验
对Controller 里的方法的多个参数进行校验 (扁平化参数):在Controller类上加注解@Validated
@RestController
@RequestMapping
@Validated
public class HelloController {
@PutMapping("/hello/id/{id}/status/{status}")
public Object helloGet(@Max(5) @PathVariable Integer id, @Min(5) @PathVariable Integer status) {
return "hello world";
}
}
|