可能你没有注意到,但关于JDK 1.5元数据注解的每一项信息,都不过是一个简单的/Hello World/教程(当然,我排除规范)。没有真正的实现。这对于处理注解的代码来说尤其如此。我将此类应用程序称为注解读取器。
我在公开抱怨这件事时,Cedric Beust在他的博客上回答了我,并给了我一些提示。
我想根据我在Hibernate注解开发中的经验,对Cedric的列表添加一些更多技巧和注释。
- 默认值,关于null怎么办?
- 程序化地伪造一个不存在的注解
- 没有扩展,关于代码重复呢?
默认值,关于null怎么办?
没有声明的东西,除了Cedric是对的。整个要点是应该有一种机制让注解读取器知道用户是设置了值还是使用了默认值。然而,JSR没有提供这样的机制。
作为一个变通方法,我还构建了自己的BooleanImplied枚举类型,它负责处理用户没有明确设置的值。对于字符串,我并不真的喜欢使用一些受限制的关键字字符串,如#implied
。目前,我的特殊关键字字符串运行得很好,但有一天它将面临“如果我想使用该关键字作为值怎么办”的问题。我没有更好的答案 :-(
程序化地伪造一个不存在的注解
在注解读取器代码中,没有方法可以程序化地构建一个新的注解/实例/,无论是有默认值还是没有。当然,注解是一个接口,没有提供实现!然而,构建自己的注解可能是有用的,可以使代码尽可能通用,并伪造用户提供的注解。(特别是如果大多数注解都有默认值)。
让我们看看一个例子
public @interface Column { /** * column name * default value means the name is guessed. */ String name() default "#implied"; /** * column length * Default value 255 */ int length() default 255; [...] } public @interface Property { /** * column(s) used * Default is guessed programmatically based on the default @Column() */ Column[] columns() default {}; [...] } @Property() public String getMyProperty() { return myProperty; }
我想在用户没有在@property()中设置任何@Column()数组时使用@Column()的默认值。为什么?因为我想要最简单的注解,我想让隐式和默认值成为规则,并且我不想重复我的注解处理代码。
两种解决方案
- 在值持有类中某个地方复制默认值(不喜欢这种方式)
- 在某个地方保留一个包含默认 @Column() 的伪方法
/** * Get default value for annotation here * (I couldn't find a way to build an annotation programatically) */ @Column() private static void defaultColumnAnnHolder() {}
这样你就可以在注解读取器代码中读取和重用这个方法。我认为一些字节码操作框架可以轻松优雅地提供这样的功能,而无需在代码中添加丑陋的额外方法。(如果我能理解Bill Burke的话,Javassist 3.0应该可以做到这一点,但我还没有测试过)
没有扩展,关于代码重复呢?
那真的很糟糕。注解不允许使用 extends 关键字。没有它,我们无法构建处理注解层次结构的泛型方法。再见了,面向对象的美好。
假设我有 @CoreProperty() 和然后 @ComplexProperty()
public void handleProperties(CoreProperty ann) { [...] }
这无法处理 @ComplexProperty(),因为没有让 @ComplexProperty() 继承 @CoreProperty() 的方法。
一些解决方案
- 强迫用户在其注解的方法上设置 和 @ComplexProperty()(我不认为这是一个选项)
- 使用组合
- 将注解数据复制到一个新的类层次结构中(或将其扁平化)
- 在注解上使用反射
第二个方案很简单,但可能不适合每个模型
@ComplexProperty( @CoreProperty(...) )
如果 CoreProperty 是一个实现概念,那么在注解中展示它会很丑陋。
第三个解决方案有点糟糕,但我现在正在使用它。注解读取器必须用两层实现
- 一层读取和扁平化注解(或将它放入一个额外的类层次结构中)
- 一个处理扁平(或层次)表示的属性的过程方法
public void processPropertyAnn(Ann property) { [...] String name = property.name(); processFlatProperty(name, null); } /** * Process a property * @param name property name * @param extraValue this extraValue can be set using ComplexProperty */ private void processFlatProperty(String name, String extraValue) { [...] }
这导致了一些额外的元数据结构:古老的“DTO是邪恶的”问题出现在注解中!
第四个解决方案可能很复杂且难以阅读,在我看来,在这种情况下反射可能有些过度。
是时候总结了
好吧,我专注于JSR 175的实际缺陷,并提供了一些使生活更轻松的建议。我仍然坚信注解是好的,并将为我们提供一种在应用程序中考虑元数据的新方式。我不在乎哪个最好:XDoclet 或 JSR 175,我关心一个 标准 元数据设施(这个主题已经足够成熟,可以进行标准化)。我关心一个 标准 元数据描述,以便轻松地将工具集成到像Hibernate这样的框架上。
这个规范将会发展。但就目前而言,不要诅咒它,用它来解决问题!