4、如何校验级联属性
?
在实际开发中,其实大多数情况下我们方法入参是个对象(甚至对象里面有对象),而不是单单平铺的参数,因此就介绍一个级联属性校验的例子:
@Getter @Setter @ToString public class Person { @NotNull private String name; @NotNull @Positive private Integer age; @Valid // 让InnerChild的属性也参与校验 @NotNull private InnerChild child; @Getter @Setter @ToString public static class InnerChild { @NotNull private String name; @NotNull @Positive private Integer age; } } public interface HelloService { String cascade(@NotNull @Valid Person father, @NotNull Person mother); } @Slf4j @Service @Validated(Default.class) public class HelloServiceImpl implements HelloService { @Override public String cascade(Person father, Person mother) { return "hello cascade..."; } }
运行测试用例:
@Test public void test1() { helloService.cascade(null, null); }
输出如下;
cascade.father: 不能为null, cascade.mother: 不能为null
此处说明一点:若你father
前面没加@NotNull
,那打印的消息只有:cascade.mother: 不能为null
我把测试用例改造如下,你继续感受一把:
@Test public void test1() { Person father = new Person(); father.setName("fsx"); Person.InnerChild innerChild = new Person.InnerChild(); innerChild.setAge(-1); father.setChild(innerChild); helloService.cascade(father, new Person()); }
错误消息如下(请小伙伴仔细观察和分析缘由):
cascade.father.age: 不能为null, cascade.father.child.name: 不能为null, cascade.father.child.age: 必须是正数
思考:为何mother的相关属性以及子属性为何全都没有校验呢?
5、循环依赖问题
上面说了Spring对@Validated的处理和对@Aysnc的代理逻辑是差不多的,有了之前的经验,很容易想到它也存在着如题的问题:比如HelloService的A方法想调用本类的B方法,但是很显然我是希望B方法的方法校验是能生效的,因此其中一个做法就是注入自己,使用自己的代理对象来调用:
vpublic interface HelloService { Object hello(@NotNull @Min(10) Integer id, @NotNull String name); String cascade(@NotNull @Valid Person father, @NotNull Person mother); } @Slf4j @Service @Validated(Default.class) public class HelloServiceImpl implements HelloService { @Autowired private HelloService helloService; @Override public Object hello(@NotNull @Min(10) Integer id, @NotNull String name) { helloService.cascade(null, null); // 调用本类方法 return null; } @Override public String cascade(Person father, Person mother) { return "hello cascade..."; } }
运行测试用例:
@Test public void test1() { helloService.hello(18, "fsx"); // 入口方法校验通过,内部调用cascade方法希望继续得到校验 }
运行报错:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'helloServiceImpl': Bean with name 'helloServiceImpl' has been injected into other beans [helloServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean ...
这个报错消息不可为不熟悉。关于此现象,之前做过非常非常详细的说明并且提供了多种解决方案,所以此处略过。
若关于此问的原因和解决方案不明白的,请移步此处:【小家Spring】使用@Async异步注解导致该Bean在循环依赖时启动报BeanCurrentlyInCreationException异常的根本原因分析,以及提供解决方案
虽然我此处不说解决方案,但我提供问题解决后运行的打印输出情况,供给小伙伴调试参考,此举很暖心有木有:
javax.validation.ConstraintViolationException: cascade.mother: 不能为null, cascade.father: 不能为null ...
总结
本文介绍了Spring提供给我们方法级别校验的能力,在企业应用中使用此种方式完成绝大部分的基本校验工作,能够让我们的代码更加简洁、可控并且可扩展,因此我是推荐使用和扩散的~
在文末有必要强调一点:关于上面级联属性的校验时使用的@Valid注解你使用@Validated可替代不了,不会有效果的。
至于有小伙伴私信我疑问的问题:为何他Controller方法中使用@Valid和@Validated均可,并且网上同意给的答案都是都可用,差不多???还是那句话:这是下篇文章的重点,请持续关注~
稍稍说一下它的弊端:因为校验失败它最终采用的是抛异常方式来中断,因此效率上有那么一丢丢的损耗。but,你的应用真的需要考虑这种极致性能问题吗?这才是你该思考的~