目录
@Validated跟@Valid的区别
实际生产应用
ConstraintViolationException和 MethodArgumentNotValidException异常
在使用前先了解下@Validated跟@Valid的区别,我们点开注解就能看出两者的不同
// Target代表这个注解能使用在类/接口/枚举上,方法上以及方法的参数上
// 注意注意!!!! 它不能注解到字段上
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
// 在运行时期仍然生效(注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在)
@Retention(RetentionPolicy.RUNTIME)
// 这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员。
@Documented
public @interface Validated {
// 校验时启动的分组
Class>[] value() default {};
}
// 可以作用于类,方法,字段,构造函数,参数,以及泛型类型上(例如:Main<@Valid T> )
// 简单来说,哪里都可以放
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface Valid {
//没有提供任何属性
}
1、来源不同 @Valid 是javax.validation
包下,而@Validated是spring自生的注解
2、作用范围不同 @Validated无法作用在字段上面
3、注解中的属性不同,如果我们需要分组校验,就需要使用@Validated
接下来我们在使用中去了解 Validation ,我们先实现自定义的校验,项目中会有很多状态枚举,我们规定前端传入状态规定特定的值,去实现校验。
首先引入jar
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-validation
先自定义个注解
package com.example.demotest.validator.annotate;
import com.example.demotest.validator.StatusValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy ={StatusValidator.class})
public @interface Status {
String[] statusType() default {};
String message() default "状态传递有误";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
}
新建自定义校验器,也是注解中validatedBy 指定的
package com.example.demotest.validator;
import com.example.demotest.validator.annotate.Status;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
public class StatusValidator implements ConstraintValidator {
private List typeStatus ;
@Override
public void initialize(Status constraintAnnotation) {
typeStatus = Arrays.asList(constraintAnnotation.statusType());
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
if(value !=null){
if(!typeStatus.contains(String.valueOf(value))){
return false;
}
}
return true;
}
}
测试实体类新建
package com.example.demotest.vo;
import com.example.demotest.validator.annotate.Status;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class IndexVO {
@Status(statusType = {"1","2"})
private Integer loginStatus;
@NotBlank(message = "id不能为空")
private String id;
@NotBlank(message = "名称不能为空")
private String name;
}
测试接口新建
package com.example.demotest.controller;
import com.example.demotest.vo.IndexVO;
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
@RequestMapping("test")
public class TestController {
@RequestMapping("index")
public String test(@Valid @RequestBody IndexVO indexVO){
return indexVO.getName()+" login success";
}
}
这里我们统一捕获下异常,这里需要捕获两种校验异常,具体什么时候会出现下面的异常,后面再说
package com.example.demotest.config;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
@RestControllerAdvice
public class ExceptionHandle {
@ExceptionHandler(value = {ConstraintViolationException.class, MethodArgumentNotValidException.class})
public String handleValidator(Exception e){
String msg = null;
if (e instanceof MethodArgumentNotValidException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
// getFieldError获取的是第一个不合法的参数(P.S.如果有多个参数不合法的话)
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null) {
msg = fieldError.getDefaultMessage();
}
} else if (e instanceof ConstraintViolationException) {
/*
* ConstraintViolationException的e.getMessage()形如
* {方法名}.{参数名}: {message}
* 这里只需要取后面的message即可
*/
msg = e.getMessage();
if (msg != null) {
int lastIndex = msg.lastIndexOf(':');
if (lastIndex >= 0) {
msg = msg.substring(lastIndex + 1).trim();
}
}
/// ValidationException 的其它子类异常
} else {
msg = "处理参数时异常";
}
return msg;
}
}
不仅可以校验controller这种,也可以校验普通的方法,上面说的 ConstraintViolationException异常就是通过普通方法校验抛出的异常,普通方法校验,只有类上添加了@Vlidated
注解,并且待校验的JavaBean上添加了@Valid
的情况下校验才会生效。
如果我们在校验逻辑中使用到了springBean对象,我们可以在校验器中使用注解生成bean
例如上面的检验器,就可以和数据库或者中间件打交道了
package com.example.demotest.validator;
import com.example.demotest.validator.annotate.Status;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
@Component
public class StatusValidator implements ConstraintValidator {
@Autowired
private UserService userService;
private List typeStatus ;
@Override
public void initialize(Status constraintAnnotation) {
typeStatus = Arrays.asList(constraintAnnotation.statusType());
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
if(value !=null){
if(!typeStatus.contains(String.valueOf(value))){
return false;
}
}
return true;
}
}