1 @Repeatable

     jdk8之前不存在可重复注解,即一个注解不能出现在同一个元素(Class、Method、Constructor、Field)2次以上。下面的写法是被禁止的:

// @ComponentScan重复使用
@ComponentScan(basePackages = "com.focuse.component1")
@ComponentScan(basePackages = "com.focuse.component2")
public class RepeatableTest {

}

如果想添加多个相同注解,只能用容器注解。容器注解提供了一个数组,来承载多个注解

@ComponentScans(value = { @ComponentScan(basePackages = "com.focuse.component1"),
        @ComponentScan(basePackages = "com.focuse.component2") })
public class RepeatableTest {
    
}

jdk8引入了@Repeatable元注解。被@Repeatable修饰的注解即是可重复使用在同一个元素上的。在jdk8中@ComponentScan就被@Repeatable修饰了,这样第一种写法在jdk8中被允许的。

2 get[Declared]Annotation、get[Declared]Annotations、get[Declared]AnnotationsByType

    java的反射包中获取一个元素(Class、 Method、Constructor、Field)上的注解对象主要有这6个方法getAnnotation(Class<T> annotationClass)、getAnnotations()、getAnnotationsByType(Class<T> annotationClass)、getDeclaredAnnotation(Class<T> annotationClass)、getDeclaredAnnotations()、getDeclaredAnnotationsByType(Class<T> annotationClass)。这些方法获取的都是运行时注解。

    在介绍这6个方法前,先描述下几个概念(翻译自jdk的注释):

  • directly present:"直接修饰"注解是指元素的RuntimeVisibleAnnotations、RuntimeVisibleParameterAnnotations或RuntimeVisibleTypeAnnotations属性包含的注解(RuntimeVisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeVisibleTypeAnnotations是classfile结构的属性),
    说白了就是指直接修饰在某个元素上的注解;
  • indirectly present:"间接修饰"注解就是指得容器注解的数组中指定的注解;
  • present:并不是"直接修饰"注解和"间接修饰"注解的合集,而是"直接修饰"注解和父类继承下来的"直接注解"的合集;
  • associated:"关联"是"直接修饰"注解、"间接修饰"注解以及父类继承下来的注解的合集;

理解了这几个概念后,以上6个方法返回的注解对象可用下表来表示

方法directly presentindirectly presentpresentassociated
getAnnotation  * 
getAnnotations  * 
getAnnotationsByType   *
getDeclaredAnnotation*   
getDeclaredAnnotations*   
getDeclaredAnnotationsByType**  

举个例子getAnnotation返回的是"直接修饰"注解和继承的注解的合集,不包括容器注解里包含的注解;而getDeclaredAnnotation仅仅返回"直接修饰"注解。

3 @Repeatable对上述方法返回值的影响

    如果一个注解是可重复的,当它在同一个元素E上出现2次以上时(注意出现1次的时候并不会默认生成容器注解),javac编译以后,class文件里面实际是该注解对应的容器注解修饰在元素E上,而容器注解中的value存储多个注解的值。举个例子:

a 自定义可重复的注解@RepeatAn

   @RepeatAn的容器是@RepeatAns

package com.focuse.jdkdemo.annation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(ElementType.METHOD)
@Retention(RUNTIME)
@Repeatable(RepeatAns.class)
public @interface RepeatAn {
}

b 定义容器注解@RepeatAns

package com.focuse.jdkdemo.annation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(ElementType.METHOD)
@Retention(RUNTIME)
public @interface RepeatAns {
    RepeatAn[] value();
}

c 使用注解@RepeatAn

package com.focuse.jdkdemo.annation;

import java.lang.reflect.Method;

/**
 * @author :focuse
 * @date :Created in 2020/2/16 上午11:10
 * @description:
 * @modified By:
 */
public class RepeatableTest<T extends  String> {

    @RepeatAn()
    @RepeatAn()
    public void method(String s) {

    }

    public static void main(String[] args) throws Exception{
        Method method = RepeatableTest.class.getDeclaredMethod("method", String.class);
        RepeatAn an = method.getAnnotation(RepeatAn.class);
        System.out.println(an);
        System.out.println("****************************************");
        RepeatAns ans = method.getAnnotation(RepeatAns.class);
        System.out.println(ans);
        System.out.println("****************************************");
        RepeatAn[] anArr = method.getAnnotationsByType(RepeatAn.class);
        System.out.println(anArr);

    }
}

运行结果

因为可重复注解出现2次以上时,编译以后相当于

@RepeatAns(value = {@RepeatAn(), @RepeatAn()})

所以: 

  • getAnnotation获取的是"直接修饰"注解和继承下来的注解,这其中没有@RepeatAn所以为null;
  • 同理,getAnnotation(RepeatAns.class)却可以获取到;
  • getAnnotationsByType(RepeatAn.class)获取的是"关联"注解(包括"间接"注解),所以获取@RepeatAn的数组;

d 编译之后的classfile文件

    最后我们来看看编译之后的classfile文件到底是什么样的,用命令"javap -c -v -l RepeatableTest.class"反编译class文件得到如下信息

"method"方法最后有个属性RuntimeVisibleAnnotations就是存放注解的,#24、#25、#26是类文件常量池中索引,具体含义如下 

 可以看到#24对应Lcom/focuse/jdkdemo/annation/RepeatAns即@RepeatAns,#25对应"value"字符串,#26对应@RepeatAn。n那么RuntimeVisibleAnnotations中信息组合起来就是@RepeatAns(value=[@RepeatAn,@RepeatAn]),与例子中现象一致。

PS:注意可重复注解在同一个元素上只出现1次的时候并不会默认生成容器注解

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐