Spring方法级别数据校验:@Validated + MethodValidationPostProcessor

Spring方法级别数据校验:@Validated + MethodValidationPostProcessor

前言

你在书写业务逻辑的时候,是否会经常书写大量的判空校验。比如Service层或者Dao层的方法入参、入参对象、出参中你是否都有自己的一套校验规则?比如有些字段必传,有的非必传;返回值中有些字段必须有值,有的非必须等等~

如上描述的校验逻辑,窥探一下你的代码,估摸里面有大量的if else吧。此部分逻辑简单(因为和业务关系不大)却看起来眼花缭乱(赶紧偷偷去喵一下你自己的代码吧,哈哈)。在攻城主键变大的时候,你会发现会有大量的重复代码出现,这部分就是你入职一个新公司的吐槽点之一:垃圾代码

若你追求干净的代码,甚至有代码洁癖,如上众多if else的重复无意义劳动无疑是你的痛点,那么本文应该能够帮到你。
Bean Validation校验其实是基于DDD思想设计的,我们虽然可以不完全的遵从这种思考方式编程,但是其优雅的优点还是可取的,本文将介绍Spring为此提供的解决方案~

效果示例

在讲解之前,首先就来体验一把吧~

@Validated(Default.class)
public interface HelloService {
    Object hello(@NotNull @Min(10) Integer id, @NotNull String name);
}

// 实现类如下
@Slf4j
@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public Object hello(Integer id, String name) {
        return null;
    }
}

向容器里注册一个处理器:

@Configuration
public class RootConfig {
    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
}

测试:

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
    @Autowired
    private HelloService helloService;

    @Test
    public void test1() {
        System.out.println(helloService.getClass());
        helloService.hello(1, null);
    }
}

结果如图:
在这里插入图片描述
完美的校验住了方法入参。

注意此处的一个小细节:若你自己运行这个案例你得到的参数名称可能是hello.args0等,而我此处是形参名。是因为我使用Java8的编译参数:-parameters(此处说一点:若你的逻辑中强依赖于此参数,务必在你的maven中加入编译插件并且配置好此编译参数

若需要校验方法返回值,改写如下:

    @NotNull
    Object hello(Integer id);

    // 此种写法效果同上
    //@NotNull Object hello(Integer id);

运行:

javax.validation.ConstraintViolationException: hello.<return value>: 不能为null
...

校验完成。就这样借助Spring+JSR相关约束注解,就非常简单明了,语义清晰的优雅的完成了方法级别(入参校验、返回值校验)的校验。
校验不通过的错误信息,再来个全局统一的异常处理,就能让整个工程都能尽显完美之势。(错误消息可以从异常ConstraintViolationExceptiongetConstraintViolations()方法里获得的~)


MethodValidationPostProcessor

它是Spring提供的来实现基于方法MethodJSR校验的核心处理器~它能让约束作用在方法入参、返回值上,如:

public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)

官方说明:方法里写有JSR校验注解要想其生效的话,要求类型级别上必须使用@Validated标注(还能指定验证的Group)

另外提示一点:这个处理器同处理@Async的处理器AsyncAnnotationBeanPostProcessor非常相似,都是继承自AbstractBeanFactoryAwareAdvisingPostProcessor的,所以若有兴趣再次也推荐@Async的分析博文,可以对比着观看和记忆:【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)

// @since 3.1
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {
    // 备注:此处你标注@Valid是无用的~~~Spring可不提供识别
    // 当然你也可以自定义注解(下面提供了set方法~~~)
    // 但是注意:若自定义注解的话,此注解只决定了是否要代理,并不能指定分组哦  so,没啥事别给自己找麻烦吧
    private Class<? extends Annotation> validatedAnnotationType = Validated.class;
    // 这个是javax.validation.Validator
    @Nullable
    private Validator validator;

    // 可以自定义生效的注解
    public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
        Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
        this.validatedAnnotationType = validatedAnnotationType;
    }

    // 这个方法注意了:你可以自己传入一个Validator,并且可以是定制化的LocalValidatorFactoryBean哦~(推荐)
    public void setValidator(Validator validator) {
        // 建议传入LocalValidatorFactoryBean功能强大,从它里面生成一个验证器出来靠谱
        if (validator instanceof LocalValidatorFactoryBean) {
            this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
        } else if (validator instanceof SpringValidatorAdapter) {
            this.validator = validator.unwrap(Validator.class);
        } else {
            this.validator = validator;
        }
    }
    // 当然,你也可以简单粗暴的直接提供一个ValidatorFactory即可~
    public void setValidatorFactory(ValidatorFactory validatorFactory) {
        this.validator = validatorFactory.getValidator();
    }


    // 毫无疑问,Pointcut使用AnnotationMatchingPointcut,并且支持内部类哦~
    // 说明@Aysnc使用的也是AnnotationMatchingPointcut,只不过因为它支持标注在类上和方法上,所以最终是组合的ComposablePointcut

    // 至于Advice通知,此处一样的是个`MethodValidationInterceptor`~~~~
    @Override
    public void afterPropertiesSet() {
        Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
        this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
    }

    // 这个advice就是给@Validation的类进行增强的~  说明:子类可以覆盖哦~
    // @since 4.2
    protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
        return (validator != null ? new MethodValidationInterceptor(vali
剩余70%内容付费后可查看
0