将方法添加到Java注释是否安全以实现向后兼容性

时间:2014-01-17 22:35:49

标签: java annotations

我有一个库中可用的注释。我是否可以安全地在后续版本中为此注释添加新值而不破坏针对该库的先前版本编译的注释?

例如:

// version 1
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation{
    String firstValue();
    String secondValue();
}

如果我添加一个名为“String thirdValue()”的方法,我假设将需要一个默认值,因为旧版注释用户不会定义该属性。

// version 2
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation{
    String firstValue();
    String secondValue();
    String thirdValue() default "third";
}

在运行时,我有一些代码会尝试读取所有值:

Class myClass = MyObject.class;
MyAnnotation annotation = myClass.getAnnotation(MyAnnotation.class);
String firstValue  = annotation.firstValue();
String secondValue = annotation.secondValue();
String thirdValue  = annotation.thirdValue();

java规范不清楚这是否安全。 http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html “13.5.7。注释类型的演变”一节只提到注释表现为接口。

2 个答案:

答案 0 :(得分:2)

引自here

  

13.5.7。注释类型的演变

     

注释类型的行为与任何其他界面完全相同。在注释类型中添加或删除元素类似于添加或删除方法。有一些重要的考虑因素可以控制注释类型的其他更改,但这些注意事项对Java虚拟机对二进制文件的链接没有影响。相反,此类更改会影响操作注释的反射API的行为。这些API的文档指定了对底层注释类型进行各种更改时的行为。

     

添加或删除注释对Java编程语言中程序的二进制表示的正确链接没有影响。

然后在同一页面上,你会发现:

  

13.5.3。接口成员

     

向接口添加方法不会破坏与预先存在的二进制文件的兼容性。

所以我希望添加一个方法,无论是否有默认值,都不会影响根据以前版本的注释编译的代码。

好吧,我试过了。我创建了一个注释

@Retention(RetentionPolicy.RUNTIME)
@Target (ElementType.TYPE)
@interface MyAnnotation {
    String foo ();
}

我在某个类上使用此注释:

@FooAnnotation(foo = "Foo")
public class MyAnnotatedClass {
    public static void main (String[] args) {
        FooAnnotation annot = MyAnnotatedClass.class.getAnnotation(FooAnnotation.class);
        Method[] methods = FooAnnotation.class.getDeclaredMethods();
        System.out.println("Methods:");
        for (Method method : methods) {
            System.out.println(method.getName() + "() returns:\n");
            try {
                String value = (String) method.invoke(annot);
                System.out.println("\t" + value);
            } catch (Exception e) {
                System.out.println("\tERROR! " + e.getMessage());
            }
        }
    }
}

然后我编译了所有内容,程序打印出以下内容:

Methods:
foo() returns:

        Foo

然后,我在我的注释中添加了一个新方法:

@Retention(RetentionPolicy.RUNTIME)
@Target (ElementType.TYPE)
@interface MyAnnotation {

    String foo ();
    String bar ();
}

我再次编译了这个注释,因此不重新编译MyAnnotatedClass。由于编译器错误,我甚至无法编译它:MyAnnotation中新添加的方法没有默认值,因此编译器要求MyAnnotatedClass显式设置它。这就是它现在打印的内容:

Methods:
bar() returns:

        ERROR! null
foo() returns:

        Foo

结论?它还在工作!通过反射,我们证明了新方法bar()确实在新编译的注释中。因此,您可以安全地向注释添加新方法,而不会破坏已编译并链接到旧注释的现有类。

我遗漏了上例中异常生成的实际堆栈跟踪。使用最新版本的注释,这是您将获得的堆栈跟踪:

java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at example.MyAnnotatedClass.main(MyAnnotatedClass.java:16)
Caused by: java.lang.annotation.IncompleteAnnotationException: example.FooAnnotation missing element bar
        at sun.reflect.annotation.AnnotationInvocationHandler.invoke(Unknown Source)
        at com.sun.proxy.$Proxy1.bar(Unknown Source)
        ... 5 more

因此,尝试调用bar()方法会引发IncompleteAnnotationException。对于这个类,read the Javadoc非常有趣:

  

抛出以指示程序已尝试访问在编译(或序列化)注释后添加到注释类型定义的注释类型的元素。如果新元素具有默认值,则不会抛出此异常。用于反射读取注释的API可以抛出此异常。

答案 1 :(得分:1)

根据JLS 9.7中的规则,我认为添加具有默认值的元素不会导致使用注释的另一个类变为非法。