本文是关于验证的,特别是Bean验证及其解决的问题。这是对Julien Tournay的帖子的回应,该帖子基本上声称——如果我概括一下——
- Scala比Java快(他在这一点上走了很远的路——就像我们说的法语中的diable vauvert)
- Bean验证有点糟糕
- 新的Play统一验证API很棒
我通常很老很明智,不会回答这类帖子。但是,见鬼,我想再次感受一下我的青春热血!毕竟,我来自一个时代,那时The Server Side风靡一时,喷子们有真正的牙齿,事物非黑即白。那是个有趣的时光。
为什么标题是《你错过了重点,就像火星气候轨道器》?用这样一个耸人听闻的标题《Scala比Java快》,我不得不提高我的水平。此外,火星气候轨道器由于转换错误而错过了火星(请参阅此处),今天我们将谈论转换。
我不会逐点反驳,而是强调一些基本误解,并解释为什么Bean验证中的事情是这样的,以及为什么这样做是有意义的。
Java有限制
在我看来,@emmanuelbernard用他当时可用的工具创建了他的API——@skaalf
在我看来,@emmanuelbernard用他当时可用的工具创建了他的API——@skaalf
当CDI和Bean验证被设计时,我们确实推动了Java的边界,我希望我在这里和那里的自由更多。但是,Bean验证之所以如此,是因为这是Java生态系统中表达验证的最自然方式,这也是我们想要实现的目标。提供不适合生态系统的解决方案是没有意义的。
例如,在Java中,人们使用可变对象。《可变》实际上并不是这个语言中的脏话。我的理解是,Play统一验证API利用了Scala社区对不可变性的倾向。
验证、转换和序列化
在Julien所举的用例中,一个JSON字符串被反序列化,应用了转换(对于日期 - 真的讨厌JSON),然后对值进行了验证。他认为Bean Validation只做后者很奇怪,流程也不正确。
我们故意保留了这种分离。专家小组普遍认为,Bean Validation并不涉及转换,并且这些步骤((反)序列化、转换、验证)应该分离。以下是一个关键原因
- 序列化和转换仅在Java边界处(Web框架、服务端点、数据存储端点等)
- 验证既发生在这些边界处,也发生在Java边界内的关键生命周期事件中
从概念上讲,将验证与其它部分分离,使得技术组合成为可能,避免了重复 - 关于这一点后面会详细说明。重要的是,对于给定的边界,它们应该得到适当的整合。这就像编译时的内联一样。
Bean Validation的使命是不被调用
Bean Validation的设计关键之一是它被构建来集成在一个统一的堆栈中,而不仅仅是单独使用。
JPA在正确的时间透明地调用Bean Validation对您的对象进行验证。JSF在填充bean之前透明地调用Bean Validation。JAX-RS在入站和出站资源上透明地调用Bean Validation。请注意,这三项技术已经解决了序列化和转换的问题。这是它们的任务。
所以关键在于,应用程序很少需要调用Bean Validation API。如果需要,我将其视为失败或未完成的工作。
如果您需要以“火灾和忘记”的方式显式验证某些数据,Bean Validation可能不是最好的工具。这没关系。
回到JSON的例子。当JSON绑定规范出台时,我们将拥有目前存在于Play统一解析、转换和验证API中的相同集成(今天在Jackson中也可以完成,我不确定这个库提供了哪些扩展点)。尽管这三个层次序列化/转换/验证在概念上被分离 - 并且我相信它们将报告不同类型的错误以更好地跟踪 - 实现将使用Bean Validation的一些特定API来内联验证与反序列化和转换。这个API实际上存在,它是Validator.validateValue它允许您验证一个值,而不需要创建关联的POJO。它已经被Web框架使用。基本上就是Play统一验证API所做的工作。
至于JAX-B和XML绑定,我们可以这样说,其中有一个“X”,这对儿童来说并不安全。更严肃地说,我们有与XSD和Bean Validation约束之间的集成计划,但我们还没有着手实施。
Bean Validation的使命是声明约束一次
从我在Play统一验证API中看到的,您将验证的声明/实现放在序列化逻辑旁边。换句话说,您不能在不同序列化操作或甚至在不同对象转换之间共享约束声明。
Bean Validation被设计成让用户声明约束一次,并让它们在整个堆栈中进行验证。从Web表单和服务入口点到底层数据库输入/输出和模式定义。有些人甚至使用我们的元数据功能将约束传播到客户端(JavaScript)。
这样就是面向未来的,当我们添加JSON-B支持时,已经声明的约束会自动且免费地使用。
结论
Bean Validation如果孤立来看是无法理解的。它确实很有用,在独立使用时也能正常工作,但只有在从上到下集成到平台上时才能发挥其优势。我们在Java EE中这样做,并一直这样做,同时确保我们的API和SPI对其他平台也保持开放,以便它们也能做到同样的事情。Spring在这方面尤为著名。
让我们谈谈Bean Validation的一些关键特性,这些特性在Play的方法中完全没有涉及
- 国际化(i18n)
- 约束继承
- 方法验证
- 自定义和上下文敏感的编程错误报告
- 部分有效的对象图 - 在可变世界中这一点非常重要
- 等等
即使其中一些特性,也会让Play的功能大不相同。
现在,在这个背景下,如果你回顾Julien对Bean Validation的抱怨(特别是关于其设计),它们几乎都站不住脚。我必须承认,他们的组合方法比依赖于注解的Bean Validation方法更优雅。这是我最怀念的地方。
设计是选择。如果没有了解设计者的选择,你很容易误解她的设计和动机。Julien一直在比较苹果和橘子,这对于一个在世界上类型安全语言中工作的人来说有点讽刺:o)
但更多的人从整体和严肃的角度看待数据验证,这是一个好的现象。这对于应用程序开发者来说工作量减少,应用程序更安全,客户更满意。从地理位置来看,我甚至可以和Julien一起喝几轮威士忌,分享一些想法:)
和平。