Java 8 可重复注解的理解与应用
Java 8 之前如何重复使用注解
在 Java 8 之前我们不能在一个类型重复使用同一个注解,例如 Spring 的注解@PropertySource 不能下面那样来引入多个属性文件@PropertySource("classpath:config.properties")上面的代码无法在 Java 7 下通过编译,错误是: Duplicate annotation
@PropertySource("file:application.properties")
public class MainApp {}
于是我们在 Java 8 之前想到了一个方案来规避 Duplicate Annotation 的错误: 即声明一个新的 Annotation 来包裹
@PropertySource, 如 @PropertySources1@Retention(RetentionPolicy.RUNTIME)
2public @interface PropertySources {
3 PropertySource[] value();
4}然后使用时两个注解齐上阵
1@PropertySources({
2 @PropertySource("classpath:config.properties"),
3 @PropertySource("file:application.properties")
4})
5public class MainApp {
6}看上去确实挺啰嗦的,用上了注解的嵌套形式。
该如何获得上面的注解内容呢?
getAnnotation(PropertySources.class), 如1PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
2for (PropertySource propertySource: annotation.value()){
3 System.out.println(propertySource.value());
4}输出内容
classpath:config.properties
file:application.properties
Java 8 重复注解的使用改进
Java 8 看到了之前实现的繁琐之处,所以引入了一个注解的注解@Repeatable 用来标识某个注解是可被重复使用的,但是同样需要一个容器注解。基于这里的例子,如果要实现可重复的注解必须要满足两个条件- 前面的那个
@PropertySources实现仍然是必须的,且实现是一样的,用以作为@PropertySource的容器注解 - 用
@Repeatable(PropertySources.class)注解@PropertySource用以说明它的容器注解是@PropertySources.
所以新的
@PropertySource 实现如下1@Retention(RetentionPolicy.RUNTIME)
2@Repeatable(PropertySources.class) //这行建立了 @PropertySource 与 @PropertySources 的关系
3public @interface PropertySource {
4 String value();
5}@Repeatable 是 Java 8 开始提供的, 现在由它来告诉 Java @PropertySources 是 @PropertySource 的容器注解,而不需要用嵌套的方式来使用它们了,因此我们就能使用本文最初的注解形式,也就是1@PropertySource("classpath:config.properties")
2@PropertySource("file:application.properties")
3public class MainApp {}上面重复使用
@PropertySource 注解在 Java 8 下是合法的,并且实际效果完全等同于 Java 8 之前嵌套的方式。区别就是显式的写出嵌套关系,还是由 @Repeatable 来建立隐含的嵌套关系。注意,如果
@PropertySource 没有用 @Repeatable 注解而重复使用的话,会报出错误:Duplicate annotation. The declaration of 'PropertySource' does not have a valid java.lang.annotation.Repeatable annotation重复注解准备好了,现在来看使用中没有出现
@PropertySources 的情况下该如何反射得到注解内容呢?基于前面对 Java 8 前后对重复注解实现的对比,猜想着
@Repeatable(PropertySources.class) 的 @PropertySource 应该会转换成 @PropertySources({@PropertySource("xxx")}) 一样的内容实现,所以运行下上面与之前同样的反射代码1PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
2for (PropertySource propertySource: annotation.value()){
3 System.out.println(propertySource.value());
4}果不出所料,输出同样的内容
classpath:config.properties实际上在字节码中 Java 8 前后对重复注解的内部实现也确实是一样的,
file:application.properties
@Repeatable 还真就是个语法糖而已。对于在 Java 8 下重复使用 @PropertySource 注解的 MainApp 类我们用 javap -v 来查看生成的字节码,得到如下的结果➜ classes javap -v cc.unmi.MainApp最后两行显示了保留在运行时的注解,并且只有一个,内容是
Classfile /Users/Yanbin/Workspaces/github/test_repeatable_annotation/target/classes/cc/unmi/MainApp.class
Last modified Jan 16, 2017; size 450 bytes
MD5 checksum 6727710e42775e09539f1575d347f6e0
Compiled from "MainApp.java"
public class cc.unmi.MainApp
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // cc/unmi/MainApp
#3 = Class #21 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcc/unmi/MainApp;
#11 = Utf8 SourceFile
#12 = Utf8 MainApp.java
#13 = Utf8 RuntimeVisibleAnnotations
#14 = Utf8 Lcc/unmi/PropertySources;
#15 = Utf8 value
#16 = Utf8 Lcc/unmi/PropertySource;
#17 = Utf8 classpath:config.properties
#18 = Utf8 file:application.properties
#19 = NameAndType #4:#5 // "<init>":()V
#20 = Utf8 cc/unmi/MainApp
#21 = Utf8 java/lang/Object
{
public cc.unmi.MainApp();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcc/unmi/MainApp;
}
SourceFile: "MainApp.java"
RuntimeVisibleAnnotations:
0: #14(#15=[@#16(#15=s#17),@#16(#15=s#18)])
#14(#15=[@#16(#15=s#17),@#16(#15=s#18)])按编号对照前面的常量池定义,上面注解可翻译为
@PropertySources(value=[@PropertySource(value="classpath:config.properties"), @PropertySource(value="file:application.properties")])这实际上与 Java 8 之前用
@PropertySources 与 @PropertySource 嵌套的写法是完全一致的,只是在字节码描述中注解的数组值是用是用 [] 来表示。Java 8 下如何反射获得重复注解的内容
这个问题在前一节中回答了一部分,依然可通过容器注解像 Java 8 之前一样的办法反射得到重复注解的内容。虽然刚在上一节中演示了,但还是再温习一下,在 Java 8 中对于1@PropertySource("classpath:config.properties")
2@PropertySource("file:application.properties")
3public class MainApp {}源程序中只是重复使用
@PropertySource 注解,然而却是要通过 @PropertySources 来反射得到 @PropertySource 的所有内容1PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
2for (PropertySource propertySource: annotation.value()){
3 System.out.println(propertySource.value()); //获得所有的 @PropertySource 注解内容
4}这里会很令人感到诧异的:明明注解时我们使用的是
@PropertySource, 应该是用 getAnnotation(PropertySource.class) 来获得才对,可是1MainApp.class.getAnnotation(PropertySource.class); //得到的是 null
2MainApp.class.getAnnotations(); //得到的仍然是 @PropertySources通过前面的分析,为什么不能直接通过上面的 API 反射得到
@PropertySource 注解的原因当然我们已经知道了,内部实现使然。所以 Java 8 为了避免在使用重复注解时的编码与反射时的尴尬,引入了一个新的反射注解的 API
getAnnotationByType(Class<A> annotationClass), 该 API 的参数可接受 PropertySource.class, 并且返回一个 @PropertySource 的数组。最终 Java 8 推荐我们反射重复注解的途径就是下面那样
1PropertySource[] propertySources = MainApp.class.getAnnotationsByType(PropertySource.class);
2for (PropertySource propertySource: propertySources ){
3 System.out.println(propertySource.value()); //获得所有 @PropertySource 的内容
4}如果我们继续认真下去,窥探一下新的
getAnnotationsByType(Class<A> annotationClass) 在 Class.java 中的实现1public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {
2 Objects.requireNonNull(annotationClass);<br/><br/>
3 AnnotationData annotationData = annotationData();
4 return AnnotationSupport.getAssociatedAnnotations(annotationData.declaredAnnotations,
5 this,
6 annotationClass);
7}解释
MainApp.class.getAnnotationsByType(PropertySource.class) 的执行过程:- 如果能找到
@Repeatable关联的容器注解类@PropertySources, 就获得@PropertySources的所有 value(类型为@PropertySource) 值组成的数组 - 如果未有关联的容器注解类,则返回
@PropertySource本身组成的数组(只有一个元素), 此时和new PropertySource[]{MainApp.class.getAnnotation(PropertySource.class}一样的。
因此,在 Java 8 中对于可重复注解应该调用
getAnnotationsByType(Class<A> annotationClass) 来反射得到,如果是不可重复注解建议还是调用原来的 getAnnotation(Class<A> annotationClass), 因为没必要使用 getAnnotationsByType(...) 获得一个空的或 1 个元素的数组。
永久链接 https://yanbin.blog/java8-repeatable-annotations/, 来自 隔叶黄莺 Yanbin's Blog[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。