可重复注解

664 阅读3分钟

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元注解,进行可重复注解的声明,要使用可重复注解需要满足两个条件:

  1. 需要一个容器注解,例如@PropertySources
  2. 可重复注解必须标注@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,分别出现了PropertySourcesPropertySource,以及最后的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);
    }
}