将方法添加到Java注释是否安全以实现向后兼容性(Is adding a method to a Java annotation safe for backward compatibility)

编程入门 行业动态 更新时间:2024-10-27 08:35:37
将方法添加到Java注释是否安全以实现向后兼容性(Is adding a method to a Java annotation safe for backward compatibility)

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

例如:

// 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 。注释类型的演变”一节提到注释表现为接口。

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.

// 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. 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.

最满意答案

引自这里 :

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 。 阅读此类的Javadoc非常有趣:

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

Quoted from here:

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. Rather, such changes affect the behavior of reflective APIs that manipulate annotations. The documentation of these APIs specifies their behavior when various changes are made to the underlying annotation types.

Adding or removing annotations has no effect on the correct linkage of the binary representations of programs in the Java programming language.

Then again on that same page, you'll find:

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. 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. 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. 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. It is very interesting to read the Javadoc for this class:

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.

更多推荐

本文发布于:2023-08-05 14:51:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1434754.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:兼容性   注释   方法   Java   adding

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!