一、快速入门
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、Validate常用注解使用一览
- 基础对象校验
- @Null/@NotNull 对象必须为空或必须不为空
- @NotEmpty/@NotBlank 字符串或集合必须为空或必须不为空
- @Length(max= min=) String对象,长度范围设置
- @Pattern(regex=,flag=) String对象的正则表达式格式
- @URL/@Email/@SafeHtml/@UUID String对象,URL地址,Email地址,HTML字符串校验
- @DateTimeFormat String对象,必须为日期格式化
- @Size Collection对象,Size大小设置
- @Min/@Max Number对象,大于或小于条件
- @Range Decimal对象,数字大小范围设置
- @Digits(integer, fraction) Number对象,设置范围
- @Negative/@NegativeOrZero Number对象,必须为负数或为0
- @Positive/@PositiveOrZero Number对象,必须为正数或为0
- @DecimalMin/@DecimalMax Decimal对象最大最小值
- @AssertTrue/@AssertFalse Boolean对象必须为True或False
- @Past/@PastOrPresent Date对象,过去或当前时间
- @Future/FutureOrPresent Date对象,未来或当前时间
- 校验声明注解
- @Validated 用于声明java对象校验
- @Valid 用于声明
3、使用参数校验注解
public class User {
@NotEmpty(message = "用户名不可为空")
@Length(max = 10, min = 4, message = "用户名长度4-10")
private String username;
@Max(value = 99, message = "年龄超出上限")
@Min(value = 1, message = "年龄小于下限")
private Integer age;
@Pattern(regexp = "[1-9]{4}([-])\\d{1,2}([-])\\d{1,2}", message = "日期格式不正确")
private String birthday;
@Email(message = "默认的邮箱验证@Email不通过")
private String email;
}
4、控制器类添加参数校验
public class ValidateController{
@PostMapping("/getUserInfo")
public UserInfo getUserInfo(@RequestBody @Validated UserInfo userInfo){
return userInfo;
}
@GetMapping("/validate1")
public String validate1(
@Size(min = 1,max = 10,message = "姓名长度必须为1到10")
@RequestParam("name") String name,
@Min(value = 10,message = "年龄最小为10")
@Max(value = 100,message = "年龄最大为100")
@RequestParam("age") Integer age) {
return "validate1";
}
}
二、@Valid 配合 @Validated 实现嵌套验证
- @Valid 校验声明
- @Validated 校验拦截处理
Item类包含一个Prop依赖类
public class Item {
@Valid
@NotNull(message = "props不能为空")
@Size(min = 1, message = "至少要有一个属性")
private List<Prop> props;
}
public class Prop {
@NotNull(message = "pid不能为空")
@Min(value = 1, message = "pid必须为正整数")
private Long pid;
}
@RestController
public class ItemController {
@RequestMapping("/item/add")
public void addItem(@Validated Item item, BindingResult bindingResult) {
doSomething();
}
}
三、分组验证
1、Javabean 类增加两个接口
public class userInfo {
public interface Default {}
public interface Update {}
@NotNull(message = "id不能为空", groups = Update.class)
private Long id;
@NotNull(message = "名字不能为空", groups = Default.class)
@Length(min = 4, max = 10, message = "name 长度必须在 {min} - {max} 之间", groups = Default.class)
private String name;
@NotNull(message = "年龄不能为空", groups = Default.class)
@Min(value = 18, message = "年龄不能小于18岁", groups = Default.class)
private Integer age;
}
public class DemoController {
@PostMapping("/validate5")
public String addUser(@Validated(value = Resume.Default.class) @RequestBody Resume resume) {
return "validate5";
}
@PutMapping("/validate6")
public String updateUser(@Validated(value = {Resume.Update.class, Resume.Default.class}) @RequestBody Resume resume) {
return "validate6";
}
}
四、实用自定义注解整理
1、自定义事件校验器
@Target({TYPE,FIELD,METHOD,PARAMETER,ANNOTATION_TYPE,TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy = ValidTimeIntervalValidater.class)
@Documented
public @interface ValidTimeInterval {
String startTime();
String endTime();
String message() default "结束时间必须大于开始时间";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
public class ValidTimeIntervalValidater implements ConstraintValidator<ValidTimeInterval,Object> {
private LocalDateTime startTime;
private LocalDateTime endTime;
@Override
public void initialize(ValidTimeInterval constraintAnnotation) {
this.startTime = constraintAnnotation.startTime();
this.endTime = constraintAnnotation.endTime();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if(null == value) return false;
BeanWrapper beanWrapper = new BeanWrapperImpl(value);
Object start = beanWrapper.getPropertyValue(startTime);
Object end = beanWrapper.getPropertyValue(endTime);
if(null == start || null == end) return false;
int result = ((Date)end).compareTo((Date)start);
if(result>=0) return true;
return false;
}
}
}
@ValidAddress
@ValidTimeInterval(startTime="startTime",endTime="endTime",message="结束时间不能小于开始时间")
public class User {
private LocalDateTime startTime;
private LocalDateTime endTime;
}
2、自定义检验枚举类型
@Documented
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = { MultiCountryAddressValidator.class })
public @interface ValidAddress {
String message() default "{com.example.validation.ValidAddress.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
public class AddressValidator implements ConstraintValidator<ValidAddress, Address> {
public void initialize(ValidAddress constraintAnnotation) { }
@Override
public boolean isValid(Address address, ConstraintValidatorContext constraintValidatorContext) {
Country country = address.getCountry();
if (country == null || country.getIso2() == null || address.getZipCode() == null) {
return true;
}
switch (country.getIso2()) {
case "FR":
return ;
case "GR":
return ;
default:
return true;
}
}
}
}
@Data
@ValidAddress
public class Address {
@Valid
@NotNull
private Country country;
}
@Data
public class Country {
@NotNull
@Size(min = 2, max = 2)
private String iso2;
}
3、通过输入枚举校验
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValue.Validator.class)
public @interface EnumValue {
String message() default "the check enum for ${validatedValue} is invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClazz();
String checkMethod();
class Validator implements ConstraintValidator<EnumValue, Object> {
private Class<? extends Enum<?>> enumClazz;
private String checkMethod;
@Override
public void initialize(EnumValue enumValue) {
checkMethod = enumValue.checkMethod();
enumClazz = enumValue.enumClazz();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) {
return Boolean.TRUE;
}
if (enumClazz == null || checkMethod == null) {
return Boolean.TRUE;
}
Class<?> valueClass = value.getClass();
Method method = enumClazz.getMethod(checkMethod, valueClass);
if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
throw new RuntimeException(String.format("%s method return is not boolean type in the %s class", checkMethod, enumClazz));
}
if (!Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(String.format("%s method is not static method in the %s class", checkMethod, enumClazz));
}
Boolean result = (Boolean) method.invoke(null, value);
return result != null && result;
}
}
}
@Data
@EqualsAndHashCode(callSuper = false)
public class AstVirtualCoinBrandQueryDTO {
@NotEmpty(message = "品牌编码不能为空")
@EnumValue(message = "品牌编码${validatedValue}无效", enumClazz = BrandEnum.class, checkMethod = "isValidCode")
private String brandCode;
}
五、RestControllerAdvice添加校验异常响应
1、@ControllerAdvice全局异常拦截
@ControllerAdvice
public class WebExceptionHandler {
@ExceptionHandler({MethodArgumentNotValidException.class,indException.class,
ConstraintViolationException.class,HttpMessageNotReadableException.})
@ResponseBody
public JsonData MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
return JsonData.buildCodeAndMsg(BASE_VAILD_EXCEPTION.getCode(),message);
}
}
2、Web配置参数校验
@Configuration
public class WebConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
}
2、添加配置类
@Aspect
@Component
public class ParamsCheckAspect {
private static Validator validator;
static {
validator = Validation.byDefaultProvider().configure()
.messageInterpolator(new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator("validationMessages")))
.buildValidatorFactory().getValidator();
}
@Pointcut("@annotation(org.springframework.validation.annotation.Validated))")
private void validateMethod() {
}
@Before("validateMethod()")
public void before(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Set<ConstraintViolation<Object>> constraintViolations = validator.forExecutables()
.validateParameters(joinPoint.getThis(), signature.getMethod(), args);
if(CollUtil.isEmpty(constraintViolations)){
return ;
}
List<String> validate = Lists.newArrayList();
for (ConstraintViolation<Object> error : constraintViolations) {
ArrayList<Path.Node> nodes = Lists.newArrayList(error.getPropertyPath());
if(nodes.size()>0){
validate.add(nodes.get(nodes.size()-1).getName()+ error.getMessage());
}
}
if(CollUtil.isEmpty(validate)){
return ;
}
StringBuffer sb = new StringBuffer();
sb.append(joinPoint.getTarget().getClass().getSimpleName());
sb.append("#").append(signature.getMethod().getName()).append("[");
for(int i=0,size=validate.size();i<size;i++){
if(i==(size-1)){
sb.append(validate.get(i));
}else {
sb.append(validate.get(i)+",");
}
}
sb.append("]");
String result = sb.toString();
ArgumentResponseEnum.VALID_ERROR.assertFail(result,result);
}
}
public interface UserService {
UserInfo checkUserInfo(@Valid UserInfo userInfo);
}
@Service
public class UserInfoServiceImpl implements UserService {
@Override
@Validated
public UserInfo checkUserInfo( @Valid UserInfo userInfo) {
return userInfo;
}
}
### 官方文档
参考:https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#preface