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

在《深度工作》中作者提出这么一个公式:高质量产出=时间*专注度。所以高质量的产出不是靠时间熬出来的,而是效率为王

相关阅读

【小家Java】深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例
【小家Java】深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)
【小家Spring】详述Spring对Bean Validation支持的核心API:Validator、SmartValidator、LocalValidatorFactoryBean...

对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码) 前言

你在书写业务逻辑的时候,是否会经常书写大量的判空校验。比如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相关约束注解,就非常简单明了,语义清晰的优雅的完成了方法级别(入参校验、返回值校验)的校验。
校验不通过的错误信息,再来个全局统一的异常处理,就能让整个工程都能尽显完美之势。(错误消息可以从异常ConstraintViolationException的getConstraintViolations()方法里获得的~)

MethodValidationPostProcessor

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

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(validator) : new MethodValidationInterceptor()); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdwfp.html