简体   繁体   English

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

[英]Is adding a method to a Java annotation safe for backward compatibility

I have an annotation that is available in a library. 我有一个库中可用的注释。 Is it safe for me to add a new value to this annotation in a subsequent release without breaking those that compiled against the previous version of the library ? 我是否可以安全地在后续版本中为此注释添加新值而不破坏针对该库的先前版本编译的注释?

For example: 例如:

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

If I add a method called "String thirdValue()", I assume a default value will be required since the legacy annotation users will not define that property. 如果我添加一个名为“String thirdValue()”的方法,我假设将需要一个默认值,因为旧版注释用户不会定义该属性。

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

At runtime, I have some code that will attempt to read all values: 在运行时,我有一些代码会尝试读取所有值:

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

The java specification isn't clear about whether or not this is safe. java规范不清楚这是否安全。 http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html section "13.5.7. Evolution of Annotation Types" just mentions that annotations behave as interfaces. http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html“13.5.7 。注释类型的演变”一节只是提到注释表现为接口。

Quoted from here : 引自这里

13.5.7. 13.5.7。 Evolution of Annotation Types 注释类型的演变

Annotation types behave exactly like any other interface. 注释类型的行为与任何其他接口完全相同。 Adding or removing an element from an annotation type is analogous to adding or removing a method. 在注释类型中添加或删除元素类似于添加或删除方法。 There are important considerations governing other changes to annotation types, but these have no effect on the linkage of binaries by the Java Virtual Machine. 有一些重要的考虑因素可以控制注释类型的其他更改,但这些注意事项对Java虚拟机对二进制文件的链接没有影响。 Rather, such changes affect the behavior of reflective APIs that manipulate annotations. 相反,此类更改会影响操作注释的反射API的行为。 The documentation of these APIs specifies their behavior when various changes are made to the underlying annotation types. 这些API的文档指定了对底层注释类型进行各种更改时的行为。

Adding or removing annotations has no effect on the correct linkage of the binary representations of programs in the Java programming language. 添加或删除注释对Java编程语言中程序的二进制表示的正确链接没有影响。

Then again on that same page, you'll find: 然后再在同一页面上,你会发现:

13.5.3. 13.5.3。 Interface Members 接口成员

Adding a method to an interface does not break compatibility with pre-existing binaries. 向接口添加方法不会破坏与预先存在的二进制文件的兼容性。

So I would expect that adding a method, with or without default value, has no effect on code that was compiled against the previous version of your annotation. 所以我希望添加一个方法,无论是否有默认值,都不会影响根据以前版本的注释编译的代码。

Well, I tried it. 好吧,我试过了。 I created an annotation 我创建了一个注释

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

I use this annotation on some class: 我在某些类上使用此注释:

@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());
            }
        }
    }
}

Then I compiled everything and the program prints the following: 然后我编译了所有内容,程序打印出以下内容:

Methods:
foo() returns:

        Foo

Then, I added a new method to my annotation: 然后,我在我的注释中添加了一个新方法:

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

    String foo ();
    String bar ();
}

I compiled this annotation again, thereby NOT recompiling MyAnnotatedClass . 我再次编译了这个注释,因此不重新编译MyAnnotatedClass I cannot even compile it due to a compiler error: the newly added method in MyAnnotation has no default value so the compiler requires that MyAnnotatedClass explicitly sets it. 由于编译器错误,我甚至无法编译它: MyAnnotation新添加的方法没有默认值,因此编译器要求MyAnnotatedClass显式设置它。 This is what it prints now: 这是它现在打印的内容:

Methods:
bar() returns:

        ERROR! null
foo() returns:

        Foo

Conclusion? 结论? It is still working! 它还在工作! With reflection we proved that the new method bar() is indeed in the freshly compiled annotation. 通过反射,我们证明了新方法bar()确实在新编译的注释中。 So, you can safely add new methods to the annotation without breaking existing classes that were compiled and linked to your old annotation. 因此,您可以安全地向注释添加新方法,而不会破坏已编译并链接到旧注释的现有类。

I left away the actual stack trace generated by the exception in the example above. 我遗漏了上例中异常生成的实际堆栈跟踪。 With the newest version of the annotation, this is the stack trace you'll get: 使用最新版本的注释,这是您将获得的堆栈跟踪:

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

So trying to invoke the bar() method will raise an IncompleteAnnotationException . 因此,尝试调用bar()方法将引发IncompleteAnnotationException It is very interesting to read the Javadoc for this class: 阅读此类的Javadoc非常有趣:

Thrown to indicate that a program has attempted to access an element of an annotation type that was added to the annotation type definition after the annotation was compiled (or serialized). 抛出以指示程序已尝试访问在编译(或序列化)批注之后添加到批注类型定义的批注类型的元素。 This exception will not be thrown if the new element has a default value. 如果新元素具有默认值,则不会抛出此异常。 This exception can be thrown by the API used to read annotations reflectively. 用于反射读取注释的API可以抛出此异常。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM