转载 

Java 8 可重复注解的使用和理解

分类:java    617人阅读    IT小君  2020-12-08 20:47

在 Java 8 之前我们不能在一个类型重复使用同一个注解,例如 Spring 的注解 @PropertySource 不能下面那样来引入多个属性文件

@PropertySource("classpath:config.properties")
@PropertySource("file:application.properties")
public class MainApp {}

上面的代码无法在 Java 7 下通过编译,错误是: Duplicate annotation

于是我们在 Java 8 之前想到了一个方案来规避 Duplicate Annotation 的错误: 即声明一个新的 Annotation 来包裹 @PropertySource, 如 @PropertySources

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
public @interface PropertySources {
  PropertySource[] value();
}

然后使用时两个注解齐上阵

1
2
3
4
5
6
@PropertySources({
  @PropertySource("classpath:config.properties"),
  @PropertySource("file:application.properties")
})
public class MainApp {
}

看上去确实挺啰嗦的,用上了注解的嵌套形式。

该如何获得上面的注解内容呢?getAnnotation(PropertySources.class), 如

1
2
3
4
PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
for (PropertySource propertySource: annotation.value()){
  System.out.println(propertySource.value());
}

输出内容

classpath:config.properties
file:application.properties

Java 8 重复注解的使用改进

Java 8 看到了之前实现的繁琐之处,所以引入了一个注解的注解 @Repeatable 用来标识某个注解是可被重复使用的,但是同样需要一个容器注解。基于这里的例子,如果要实现可重复的注解必须要满足两个条件

  1. 前面的那个 @PropertySources 实现仍然是必须的,且实现是一样的,用以作为 @PropertySource 的容器注解
  2.  @Repeatable(PropertySources.class) 注解 @PropertySource 用以说明它的容器注解是 @PropertySources.

所以新的 @PropertySource 实现如下

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(PropertySources.class) //这行建立了 @PropertySource 与 @PropertySources 的关系
public @interface PropertySource {
  String value();
}

@Repeatable 是 Java 8 开始提供的, 现在由它来告诉 Java @PropertySources 是 @PropertySource 的容器注解,而不需要用嵌套的方式来使用它们了,因此我们就能使用本文最初的注解形式,也就是

1
2
3
@PropertySource("classpath:config.properties")
@PropertySource("file:application.properties")
public 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")}) 一样的内容实现,所以运行下上面与之前同样的反射代码

1
2
3
4
PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
for (PropertySource propertySource: annotation.value()){
  System.out.println(propertySource.value());
}

果不出所料,输出同样的内容

classpath:config.properties
file:application.properties

实际上在字节码中 Java 8 前后对重复注解的内部实现也确实是一样的,@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
2
3
@PropertySource("classpath:config.properties")
@PropertySource("file:application.properties")
public class MainApp {}

源程序中只是重复使用 @PropertySource 注解,然而却是要通过 @PropertySources 来反射得到 @PropertySource 的所有内容

1
2
3
4
PropertySources annotation = MainApp.class.getAnnotation(PropertySources.class);
for (PropertySource propertySource: annotation.value()){
  System.out.println(propertySource.value());  //获得所有的 @PropertySource 注解内容
}

这里会很令人感到诧异的:明明注解时我们使用的是 @PropertySource, 应该是用 getAnnotation(PropertySource.class) 来获得才对,可是

1
2
MainApp.class.getAnnotation(PropertySource.class); //得到的是 null
MainApp.class.getAnnotations();   //得到的仍然是 @PropertySources

通过前面的分析,为什么不能直接通过上面的 API 反射得到  @PropertySource 注解的原因当然我们已经知道了,内部实现使然。

所以 Java 8 为了避免在使用重复注解时的编码与反射时的尴尬,引入了一个新的反射注解的 API getAnnotationByType(Class<A> annotationClass), 该 API 的参数可接受 PropertySource.class, 并且返回一个 @PropertySource 的数组。

最终 Java 8 推荐我们反射重复注解的途径就是下面那样

1
2
3
4
PropertySource[] propertySources = MainApp.class.getAnnotationsByType(PropertySource.class);
for (PropertySource propertySource: propertySources ){
  System.out.println(propertySource.value()); //获得所有 @PropertySource 的内容
}

如果我们继续认真下去,窥探一下新的 getAnnotationsByType(Class<A> annotationClass) 在 Class.java 中的实现

1
2
3
4
5
6
7
8
public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) {
    Objects.requireNonNull(annotationClass);
 
    AnnotationData annotationData = annotationData();
    return AnnotationSupport.getAssociatedAnnotations(annotationData.declaredAnnotations,
                                                      this,
                                                      annotationClass);
}

解释 MainApp.class.getAnnotationsByType(PropertySource.class) 的执行过程:

  1. 如果能找到 @Repeatable 关联的容器注解类 @PropertySources, 就获得 @PropertySources 的所有 value(类型为 @PropertySource) 值组成的数组
  2. 如果未有关联的容器注解类,则返回 @PropertySource 本身组成的数组(只有一个元素), 此时和 new PropertySource[]{MainApp.class.getAnnotation(PropertySource.class} 一样的。

因此,在 Java 8 中对于可重复注解应该调用 getAnnotationsByType(Class<A> annotationClass) 来反射得到,如果是不可重复注解建议还是调用原来的 getAnnotation(Class<A> annotationClass), 因为没必要使用 getAnnotationsByType(...) 获得一个空的或 1 个元素的数组。

点击广告,支持我们为你提供更好的服务

HTML5现代家居装潢公司网站模板

js+css3抽奖转盘旋转点餐代码

html5 svg夜空中星星流星动画场景特效

jQuery右端悬浮带返回顶部特效

网页设计开发公司网站模板

css+js实现的颜色渐变数字时钟动画特效

响应式时尚单品在线商城网站模板

响应式太阳能能源公司网站模板

有机水果蔬菜HTML5网站模板

HTML5数字产品服务公司网站模板

响应式咖啡饮品宣传网站模板

html5图标下拉搜索框自动匹配代码

小众时尚单品在线电子商务网站模板

现代时尚家具公司网站模板

HTML5 Canvas竖直流动线条背景动画特效

html5 canvas进度条圆环图表统计动画特效

中小型创意设计服务公司网站模板

css鼠标跟随文字模糊特效

canvas炫酷鼠标移动文字粒子特效

html5 canvas彩色碎片组合球形旋转动画特效

点击广告,支持我们为你提供更好的服务
 工具推荐 更多»
点击广告,支持我们为你提供更好的服务