springboot整合Validate实现参数校验

一、快速入门
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、使用参数校验注解
//Bean使用注解校验使用示例
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 // 嵌套验证必须用@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;
}

//方法参数上使用@Validated进行校验处理
@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 {}

    //update数据时id是必须要有
    @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、自定义事件校验器
//自定义@ValidTimeInterval注解
@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;
        }
    }

}

//在类上使用注解,要求参数必须要包含startTime和endTime
@ValidAddress
@ValidTimeInterval(startTime="startTime",endTime="endTime",message="结束时间不能小于开始时间")
public class User {
    private LocalDateTime startTime;
    
    private LocalDateTime endTime;
}
2、自定义检验枚举类型
//自定义@ValidAddress注解
@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;
            }
        }
    }

}
//在类上使用注解,要求必须有country属性
@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 {
 
    /**
     * 处理请求参数格式错误 @RequestBody上使用@Valid 实体上使用@NotNull等,验证失败后抛出的异常是MethodArgumentNotValidException异常
     */
    @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配置参数校验
/**
 * validate参数校验默认的是一个参数校验失败后,还会继续校验后面的参数
 * 通过这个配置改成:校验参数时只要出现校验失败的情况,就立即抛出对应的异常,结束校验,不再进行后续的校验
 */
@Configuration
public class WebConfig {
 
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                //failFast的意思只要出现校验失败的情况,就立即结束校验,不再进行后续的校验。
                .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 {
    
    //手动指定校验提示资源(默认在resource目录下ValidationMessages.properties)
    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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值