注释和验证

发布者    |      

注释无疑是Java SE 5中最酷的新特性,将深刻改变我们编写Java代码的方式。在设计EJB 3.0、Hibernate Validator和Seam的过程中,我们有机会真正开始将注释的使用发挥到极致。当可以将声明和逻辑混合到同一个源文件中时,你会发现许多事物都可以更加优雅和高效地以声明性模式表达。我们注意到,在实践中,尽管人们可能对Java注释有最初的疑虑,但一旦他们开始在实际项目中使用EJB 3.0等,他们会经历生产力的显著提升,很快就会对这种方法感到舒适。

不可避免的是,注释规范在许多地方让我感到失望。两个问题已经在博客和其他地方得到了很好的记录:首先,注释成员值不能为null;其次,注释定义不支持继承。我们经常需要一个注释的null默认值,而不得不通过使用一些魔法值(如空字符串)来表示null。这是一个真正丑陋的解决方案,我真的不明白为什么JSR-175不能允许null值。当定义具有相同语义成员的注释时,缺乏继承是不方便的。我不会过多地强调这些问题,因为它们现在已经被很好地理解了。

现在,在实践中,这些问题的任何一项都不是使用注释的实际应用程序代码的大问题。这些问题主要是定义注释的框架开发者遇到的不便。所以我们可以忍受这一点。

目前JSR-175的最大/缺点/就是验证注解类功能的极度匮乏。令人惊讶的是,我没有在其他地方看到对此问题的讨论。为约束注解提供的唯一工具是@Target元注解,它指定了哪些程序元素(类、方法、字段等)可以被注解。与DTD或XML模式提供的功能相比,这显得非常原始。并且,与之前提到的限制不同,这个问题给基于注解的框架的最终用户带来了负担,而不是框架的设计者。

我们/至少/需要能够写下以下约束

  • 此注解注解实现或扩展特定类或接口的类
  • 此注解注解具有特定签名的函数
  • 此注解注解特定类型的字段
  • 此注解在一个类中最多出现一次
  • 此注解注解具有特定注解的类的方法/字段

可能,我们还可以编写更复杂的东西,例如

  • 以下注解是互斥的
  • 如果此注解出现,则必须也出现另一个注解

让我们看一些例子

  • @PersistenceContext注解仅在类型为EntityManager的字段或具有以下签名的函数上才有意义:void set<Name>(EntityManager)。如果用户将其放在其他地方,则是一个错误!
  • @PostConstruct注解只能出现在类中的一个函数上。如果用户有两个@PostConstruct函数,则是一个错误。
  • @Basic注解仅适用于@Entity类和@Embeddable类。如果它出现在会话bean上,则是一个错误。
  • @Stateful注解可能仅出现在实现Serializable接口的类上。
  • @Lob注解可能仅出现在String类型或byte[]类型字段或getter方法上
  • @Stateless和@Stateful不能在同一个类上同时出现
  • 等等...

在这些例子中,编译时可以捕获编程错误,而不是在运行时。

实际上,当前Java版本缺乏适当的约束语言已经开始导致一些人走错了路。Java EE 5草案使用名为@Resource的注解来注入各种不同类型的内容,其中许多内容在常规意义上并不是“资源”。一些“资源”需要额外信息,如authenticationType或mappedName,这些信息甚至对其他类型的“资源”没有意义。因此,@Resource注解正变成一个无关紧要的东西的集合,其中大部分与任何给定的“资源”类型都无关。这是一个语义极弱、耦合度极低的构造。每次我们发现一种新的“资源”类型时,它都变得更加复杂,耦合度更低。它是具有sendJmsMessage()、executeSqlQuery()和listInbox()等方法的Resource类注解的等价物。

如果我们有适用于Java注解的适当约束工具,Java EE 5小组就会意识到@Resource注解需要分解成几个注解,每个注解都只适用于相关的地方。

而不是这个

@Resource(authenticationType=APPLICATION) Connection bookingDatabase;

我们最终会得到这个

@Inject @AuthenticationType(APPLICATION) Connection bookingDatabase;

它使用更细粒度、更语义化、更耦合的注解。@AuthenticationType会被约束只应用于有意义的资源类型。请注意,@Inject注解由于对预期的“资源”类型不够具体,实际上更容易被未来发现依赖注入新用法的专家小组重用。

让我们希望真正可验证的注解是Java SE 6的一个特性。

第二个经常困扰我的问题是,JSR-175 没有定义一种标准的方式来覆盖 Java 代码中指定的注解,以及通过某种定义良好的外部元数据格式以统一的方式通过反射 API 访问覆盖的值。这迫使框架开发者定义自己的语言来覆盖注解,以及自己的设施来解析元数据、合并值并将合并后的值暴露给应用程序。这为什么很重要呢?

嗯,对于其他框架或甚至应用程序本身,通常很有用,可以消费 EJB3 或 Seam 等框架提供的注解。设计良好的注解可以表达关于组件语义及其在系统中的角色的信息,这对其他具有组件模型意识的通用代码很有用。例如,@Entity 注解对除了持久性之外的其他方面也很有兴趣!(Seam 就使用了它。)但 EJB3 规范没有提供简单、可靠的方法来让应用程序能够判断一个类是否为实体 bean,因为 EJB3 容器认为被注解为 @Entity 的类和部署描述中提到的类。开发一个完整的 API 来公开合并的 EJB3 元数据将使专家组忙几个月。

然而,在我看来,注解覆盖的重要性被高估了。JSR-175 的命名不妥。单词“元数据”高度重叠,因此有些人认为 Java 注解是表达系统/配置/的设施。事实上,注解明显不是表达配置的好地方!使用得当的注解可以启用/声明式编程/,这是一件完全不同的事情。我的预测是,我们将发现,在实践中,人们使用注解覆盖的频率比预期的要低得多。

例如,与一些博客和其他地方发布的关于 EJB3 的批评相反,没有人建议你应该使用注解来配置数据源或 JMS 队列!相反,注解将提供一个数据源的/逻辑名称/,该名称将在其他地方使用 XML 进行配置。逻辑名称在不同部署之间没有理由改变。另一个例子来自 ORM 的世界。几乎/永远/没有理由在不同部署的系统之间更改表和列名称。(如果这真的有必要,使用手写的 SQL 建立系统将是几乎不可能的!)因此,没有理由通过将映射信息从实体 bean 的定义中分离出来来使代码更难以理解。另一方面,模式名称和目录名称/确实/会改变,因此不属于注解。

因此,尽管我确实希望 Java SE 的未来版本提供标准的注解覆盖设施,但我可能没有它也能过得去。我的直觉是,从本质上讲,系统在不同部署之间更改的大部分信息对应用程序或附加方面来说都无关紧要。


回到顶部