Spring中开启方法级验证

在spring中只需要注册了MethodValidationPostProcessor就能开启方法级验证,在调用方法时如果参数或返回值无法满足对应的限制就无法完成调用
下面以springboot项目为例。
首先在spring容器内放入MethodValidationPostProcessor

@SpringBootApplication
public class App {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(new Object[]{App.class}, args);
    }

    @Bean
    public MethodValidationPostProcessor mvp(){
        return new MethodValidationPostProcessor();
    }
}

之后就可以开始方法级验证了

@RestController
@Validated
public class MyControler {
    @RequestMapping("/a")
    public @Size(min=1) String a(@Size(min=1) String a){
        return a;
    }
}

如上,首先需要加上Validated注解(spring4.3.2在类上加了有效,方法上加了无效),之后在方法的参数或返回值是哪个就可以加上对应的校验规则了

校验规则

spring支持jsr303以及hibernate中的校验规则
@AssertFalse 对应参数为false
@AssertTrue 对应参数为true
@DecimalMax("1") 对应小数的最大值
@DecimalMin("1") 对应小数的最小值
@Digits(integer=1,fraction = 2) 对应数字整数(integer)和小数(fraction)位数
@Future Date只接受将来的时间
@Past Date只接受过去的时间
@Max(100) 数字最大值
@Min(100) 数字最小值
@NotNull 不接受Null值
@Null 只接受Null值
@Pattern(regexp="^a$") 正则表达式regexp为表达式
@Size(min=1,max=2) 参数的长度min/max为最小/最大长度
hibernate validation
@CreditCardNumber Luhn算法校验,用于防止用户错误输入,并不真实校验信用卡号
@EAN 欧洲商品标码校验
@Email 邮箱校验
@Length 校验string的长度
@LuhnCheck Luhn算法校验
@NotBlank 字符串非null非空
@NotEmpty 字符串、集合、map非空
@ParameterScriptAssert 使用脚本进行校验支持JSR223脚本
@Range 匹配数字或表示数字的字符串的取值范围
@SafeHtml 校验是否包含恶意脚本
@URL 合法URL

异常处理

在校验遇到非法的参数/返回时会抛出ConstraintViolationException,可以通过getConstraintViolations获得所有没有通过的校验ConstraintViolation集合,可以通过它们来获得对应的消息。

实现原理

MethodValidationPostProcessor继承了AbstractBeanFactoryAwareAdvisingPostProcessor,从名字可以看出它是一个AdvisingBeanPostProcessor。
在初始化后由于实现了InitializingBean,会运行afterPropertiesSet,其中会设置pointcut和advisor,是使用aop的方法实现的校验。
在校验失败的异常堆栈中也可以看出

javax.validation.ConstraintViolationException: null
	at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:136) ~[spring-context-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE]
	at blog.MyControler$$EnhancerBySpringCGLIB$$4b158379.a(<generated>) ~[main/:na]
	...

使用的是CGLIB进行的aop