Java 8提供了可重复注解的语法糖,那么什么是可重复注解呢。
举一个例子说明:
在Spring中,可以通过注解@PropertySources引入外部资源文件,配置方式是下面这样的:
@PropertySources({
@PropertySource("application.properties"),
@PropertySource("application.yml"),
})
public class Resource {
}
这种方式看起来比较繁琐,但是在Java 8之前,只能使用上面的方式配置,为什么不可以是下面这样呢:
@PropertySource("application.properties")
@PropertySource("application.yml")
public class Resource {
}
为了解决这个问题,Java 8提供了@Repeatable元注解,进行可重复注解的声明,要使用可重复注解需要满足两个条件:
- 需要一个容器注解,例如
@PropertySources。 - 可重复注解必须标注
@Repeatable元注解。
为什么需要满足上面的两个条件呢,前面说过,Java 8提供的可重复注解功能也只是一个语法糖,其本质还是跟Java 8之前一样的,所以容器注解和元素注解都是必不可少的。
元注解@Repeatable源码如下:
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Repeatable {
Class<? extends Annotation> value();
}
元注解@Repeatable只有一个注解属性value(),这个注解属性是用于声明容器注解的,只有这样,编译器才能知道容器注解是谁。
为了更好的说明Java 8可重复注解的使用,来查看@PropertySources的源码说明:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
......
String[] value();
......
}
容器注解@PropertySources内声明了元素注解@PropertySource,而元素注解@PropertySource标注了元注解@Repeatable,并且元注解@Repeatable声明了对容器注解@PropertySources的引用,如此,就完成了一个可重复注解的声明。
所以使用下面这样的方式,在Java 8中就可以正常编译通过了:
@PropertySource("application.properties")
@PropertySource("application.yml")
public class Resource {
}
字节码证明语法糖
前面说过,这些只是语法糖而已,为了证明这一点,反编译Resource类,字节码如下:
$ javap -v Resource.class
Classfile /opt/xlp/workspace/user-center/target/test-classes/com/jusdascm/test/Resource.class
Last modified 2020年1月17日; size 524 bytes
MD5 checksum 69ccf0adbe3375a6f29b6437efeee846
Compiled from "Resource.java"
public class com.jusdascm.test.Resource
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #2 // com/jusdascm/test/Resource
super_class: #3 // java/lang/Object
interfaces: 0, fields: 0, methods: 1, attributes: 2
Constant pool:
#1 = Methodref #3.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // com/jusdascm/test/Resource
#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 Lcom/jusdascm/test/Resource;
#11 = Utf8 SourceFile
#12 = Utf8 Resource.java
#13 = Utf8 RuntimeVisibleAnnotations
#14 = Utf8 Lorg/springframework/context/annotation/PropertySources;
#15 = Utf8 value
#16 = Utf8 Lorg/springframework/context/annotation/PropertySource;
#17 = Utf8 application.properties
#18 = Utf8 application.yml
#19 = NameAndType #4:#5 // "<init>":()V
#20 = Utf8 com/jusdascm/test/Resource
#21 = Utf8 java/lang/Object
{
public com.jusdascm.test.Resource();
descriptor: ()V
flags: (0x0001) 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 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/jusdascm/test/Resource;
}
SourceFile: "Resource.java"
RuntimeVisibleAnnotations:
0: #14(#15=[@#16(#15=[s#17]),@#16(#15=[s#18])])
org.springframework.context.annotation.PropertySources(
value=[@org.springframework.context.annotation.PropertySource(
value=["application.properties"]
),@org.springframework.context.annotation.PropertySource(
value=["application.yml"]
)]
)
可以看到,在常量池中#14和#16,分别出现了PropertySources和PropertySource,以及最后的RuntimeVisibleAnnotations的内容,显示其可重复注解的结构。
可重复注解获取
可重复注解只是语法糖,那么其注解的获取方式自然也可以通过Java 8之前的方式获取,例如:
public static void main(String[] args) {
PropertySources annotation = Resource.class.getAnnotation(PropertySources.class);
PropertySource[] propertySources = annotation.value();
for (PropertySource propertySource : propertySources) {
System.out.println(propertySource);
}
}
结果输出如下:
@org.springframework.context.annotation.PropertySource(...)
@org.springframework.context.annotation.PropertySource(...)
但是这会令人感觉很奇怪,明明标注的是@PropertySource注解,为什么还需要知道容器注解@propertySources的存在,并且获取方式也相对要复杂一些。
自然,Java 8也提供了快捷的获取方式,在java.lang.reflect.AnnotatedElement中提供了一个API:
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
java中通过反射获取注解的API都是通过
AnnotatedElement这个接口定义的,Class类就实现了这个接口。
新API的获取方式:
public static void main(String[] args) {
PropertySource[] propertySources = Resource.class.getAnnotationsByType(PropertySource.class);
for (PropertySource propertySource : propertySources) {
System.out.println(propertySource);
}
}