JDK注解小结

文章目录

  • 背景
  • 注解的作用
  • 注解的声明
  • 元注解
    • @Documented
    • @Target
    • @Retention
    • @Inherited
    • @Repeatable
  • 自定义注解使用流程

背景

如今常用的Java框架中已经使用到了大量的注解(Annotation) 这些自定义的注解都是通过JDK提供的元注解构建了, 于是花了一点时间研究了一下JDK1.8 中的元注解, 在此总结一下:

注解的作用

注解从字面上的意思理解为注释, 解释. 就是在源代码中添加一些额外的信息(比如@author等用在源文件中). 其实Java注解的功能远不止如此, 本质上讲注解是往源代码中添加了一些额外的信息, 以便于在其他的类中可以将这些信息获取出来加以使用. 总的来讲注解有三个作用:

  1. 帮助生成API文档. 注解可以看成一些特殊的标签, 方便使用javadoc工具将源代码中的一些注释生成API文档, 比如使用@Documented 标注的注解生成的API文档中就可以看见.
  2. 代码跟踪, 替代配置文件. 这应该是注解最重要的一个作用, 因为注解中可以包含一些数据, 这些数据可以代表配置信息, 使用反射可以提取这些信息. 因此使用注解我们就可以不需要额外的配置文件.
  3. 编译时格式检查. 比如@Override 这样的注解 表示该方法覆盖父类, 如果不是覆盖父类则编译时会报错.
    总之注解赋予了源代码更多的信息, 而通过灵活的使用这些信息可以帮助更好的编码.

注解的声明

下面来看一个最简单的注解声明

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface User {
    // 使用@interface 定义一个注解 注解之中定义的都是方法 每一个方法其实是声明的参数 方法名称即是参数名称 方法返回类型即是参数类型
    // 参数类型可谓基本类型 Class类型 Enum(枚举) String Annotaion 或者以上的数组[],不能为任何封装的类型以及自定义的类
    // 方法后可以接default 指定方法的默认值
     // 非基本类型如String 素默认值不能为null
    // 所有的自定义注解都是继承自 Annotation 类型 且不可再继承其他接口以及注解. 代码中不直接写明继承自Annotation 这个过程的细节是编译器完成的
    String name() default "hello";
}

首先注解的类型是@Interface 声明的时候不要和接口弄混淆了, 注解的顶部是一些其他的注解, 此处使用到的是元注解(后面会解释) 注解中定义的是方法, 首先方法的名称代表的是这个注解的参数, 方法返回的数据类型则是这个参数的类型, 返回类型有严格的限制, 类型为以下的几种类型:1. 基本类型(short long float double int char boolean byte) 2. Class 类型 3. enum 枚举类型 4. Annotation 类型 5. String 类型 6. 以上几种类型构成的数组[]. 所有的注解都自动继承自java.lang.annotation.Annotation 接口, 这个接口在声明的时候不写出,且不允许自定义注解继承其他类或者实现其他接口. 注解中的方法允许存在默认值, default value.2. Class 类型 3. enum 枚举类型 4. Annotation 类型 5. String 类型 6. 以上几种类型构成的数组[]. 所有的注解都自动继承自java.lang.annotation.Annotation 接口, 这个接口在声明的时候不写出,且不允许自定义注解继承其他类或者实现其他接口. 注解中的方法允许存在默认值, default value.

元注解

上面的例子中我们看到在声明注解的那一行(@interface)上面使用到了三个注解, 这三个注解就是元注解.元注解构成所有的其他自定义注解. 所以要明白其它自定义注解的作用和意义, 需要清楚元注解的作用. JDK1.8 中包含了五种元注解@Target @Retention @Documented @Inherited @Repeatable 下面分别对这五种注解功能进行解释说明:

@Documented

这个注解是最简单的元注解, 它表示使用此注解标注的自定义注解再通过javadoc工具生成API文档的时候会出现在API文档里面. 举个例子@Deprecated 这个注解再Java中非常常见.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

看它的声明里面就有@Documented, 所以生成的API文档里面存在deprecated 的方法的地方都能够看到这个注解.

@Target

这个元注解是非常重要的元注解, 字面意思为"目标" 指定的是注解的有效范围, 我们看这个注解的声明:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

它只存在一个方法, 返回的类型是ElementType 这个枚举类型, 这个枚举类型取值有:

public enum ElementType {
   /** Class, interface (including annotation type), or enum declaration */
   TYPE,

   /** Field declaration (includes enum constants) */
   FIELD,

   /** Method declaration */
   METHOD,

   /** Formal parameter declaration */
   PARAMETER,

   /** Constructor declaration */
   CONSTRUCTOR,

   /** Local variable declaration */
   LOCAL_VARIABLE,

   /** Annotation type declaration */
   ANNOTATION_TYPE,

   /** Package declaration */
   PACKAGE,

   /**
    * Type parameter declaration
    *
    * @since 1.8
    */
   TYPE_PARAMETER,

   /**
    * Use of a type
    *
    * @since 1.8
    */
   TYPE_USE
}

从上往下可以很容易理解它指明的作用范围:1.TYPE作用于类上 2.FIELD作用于成员变量 3.METHOD 作用于成员方法 4.PARAMETER 作用于方法参数上 5.CONSTRUCTOR 作用于构造函数 6.LOCAL_VARIABLE 作用于局部变量 7. ANNOTATION_TYPE 作用于注解上, 比如@Target 这个元注解, 它的@Target 取值是ANNOTATION_TYPE 8.PACKAGE 作用于包的注解.

@Retention

这个注解可以理解为保留时间(生命周期) 看它的声明中也只有一个方法:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

返回值类型依然是一个枚举类型:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

这三个值也比较好理解 1. SOURCE 表示该注解只是存在于源代码中, 编译时会舍弃 2. CLASS 表示该注解编译时会编译到CLASS文件中, 但是JVM加载CLASS文件时会舍弃 3.RUNTIME 表示JVM加载CLASS文件时会加载该注解, 因此可以通过反射获取这些注解信息. 绝大多数的注解生命周期类型都是RUNTIME这一种.

@Inherited

与@Documented 注解一样, 它只是起标记作用, 此注解声明的时候内部没有定义函数, 所以在使用时不需要传递参数.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

该注解表示的是使用含有该元注解去标注的类, 在其继承的子类中也是自动继承此注解. 有两个点特别重要:1. 注解要生效必须标注在类上面, 方法以及成员变量是无效的 2. 标注在interface上面其实现类是无法继承改接口. 此注解提供了一种向上查询的机制, 当在某一个类上查询此类型注解时, 会向上查询, 知道找到含有此标注的父类或者是找不到此标注的父类才结束. 举个例子
首先声明一个@Inherited 标记的注解

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestInherit {
    String value() default "hello";
}

然后用这个注解去标注一个类

@TestInherit("the test string")
public class UseAnnotation {
    private String name;
    public UseAnnotation(){

    }
    public UseAnnotation(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
}

将这个类称为标记的父类.
使用反射去获取标记信息

public static void main(String[] args) throws ClassNotFoundException{
        Class clazz = Class.forName("com.myannotation.testinherited.UseAnnotation");
        if(clazz.isAnnotationPresent(TestInherit.class)){
            System.out.println("inherited");
            TestInherit t = (TestInherit) clazz.getAnnotation(TestInherit.class);
            System.out.println(new UseAnnotation(t.value()).getName());
        }else{
            System.out.println("not inherited");
        }
    }

看输出结果:

inherited
the test string

这个理所当然, 现在扩展这个标记父类

public class SubUseAnnotation extends UseAnnotation {
    private int age;
    public SubUseAnnotation(String name, int age){
        super(name);
        this.age = age;
    }

    public int getAge(){
        return this.age;
    }
}

然后使用反射在这个子类上面获取注解信息

public static void main(String[] args) throws ClassNotFoundException{
        Class clazz = Class.forName("com.myannotation.testinherited.SubUseAnnotation");
        if(clazz.isAnnotationPresent(TestInherit.class)){
            System.out.println("inherited");
            TestInherit t = (TestInherit) clazz.getAnnotation(TestInherit.class);
            System.out.println(new SubUseAnnotation(t.value(), 10).getName());
        }else{
            System.out.println("not inherited");
        }
    }

输出结果

inherited
the test string

可见在继承的子类中也可以获取到此注解.

@Repeatable

先看这个注解的声明:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the containing annotation type for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

参数范围类型为存储容器注解类型. 表示此注解修饰的注解是可重复的修饰某一个元素, 传递的参数即是该可重复注解的存储容器注解类型. 可重复很好理解, 就是在某一个元素上面可以多次使用这个注解. 比如先定义一个普通的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NormalAnn {
    String value() default "";
}

然后重复在一个类上面使用这个注解:

@NormalAnn("hello")
@NormalAnn("world")
public class UseNormalClass {
}

此时IDE中会报错, 错误信息是:XXXX does not have a valid Repeatable annotation 即是没有使用@Repeatable 标注的注解是不能够在其修饰的元素上面重复使用. 首先明白要使用这个重复标注功能 需要一个可重复标注的注解以及存储该注解的容器, 定义这个可重复标注的注解如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(RepeatContainer.class)
public @interface RepeatAnn {
    String name() default "";
    int age() default -1;
}

指定了Container为RepeatContainer 然后定义的RepeatContainer如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatContainer {
    RepeatAnn[] value();
}

指的注意的一点是这个存储容器的@Target是重复标注(此处为RepeatAnn的@Target的子集, 其次这个容器中需要有一个value方法其返回值为重复标注(RepeatAnn) 构成的数组. 下面在一个类中使用这个可重复的标注

public class UseRepeatAnn {

    @RepeatAnn(name="aa", age=1)
    @RepeatAnn(name="bb", age=2)
    public void method1(){

    }

    @RepeatAnn(name="cc", age=3)
    public void method2(){

    }
}

编译器没有报错, 注解使用没有问题. 下面来看如何正确的通过反射来获取这些重复的注解

public class UseRepeatAnn {

    @RepeatAnn(name="aa", age=1)
    @RepeatAnn(name="bb", age=2)
    public void method1(){

    }

    @RepeatAnn(name="cc", age=3)
    public void method2(){

    }
    public static void main(String[] args) throws NoSuchMethodException {
        Class clazz = UseRepeatAnn.class;
        Method m = clazz.getMethod("method1");
        System.out.println("-----------------case1----------------");
        Annotation t1 = m.getAnnotation(RepeatContainer.class);
        System.out.println(t1);// 正确的访问方式 获取到这个容器注解
        for(Annotation item : ((RepeatContainer) t1).value()){
            System.out.println(((RepeatAnn)item).age());
        }
        System.out.println("-----------------case2----------------");
        Annotation t2 = m.getAnnotation(RepeatAnn.class);
        System.out.println(t2);// 错误的访问方式输出为null 因为这些注解存储在容器里面 不是表面上直接标注于方法上

        System.out.println("-----------------case3----------------");
        Annotation[] t3 = m.getAnnotations();
        System.out.println(t3.length);// 这个长度为1 因为返回的是容器类型 RepeatContainer

        for(Annotation item : ((RepeatContainer)t3[0]).value()){
            System.out.println(((RepeatAnn)item).age());
        }

        System.out.println("-----------------case4----------------");
        Annotation[] t4 = m.getAnnotationsByType(RepeatAnn.class);
        System.out.println(t4.length);
        for(Annotation item : t4){
            System.out.println(((RepeatAnn)item).age());
        }

    }
}

自定义注解使用流程

自定义注解使用流程包含:1.声明一个注解;2.将注解标注在对应的元素上并添加信息;3.使用反射获取标记元素上的注解信息加以使用.关于注解的声明以及标注在对应的元素上面前面的例子已经说过, 下面主要是说一下反射获取元素上的注解信息.
查看反射Class中的关于注解操作的方法,主要方法如下
1.isAnnotationPresent(Class annotationClass) 判断该元素是否受到指定注解类型(annotationClass)的标注.
2.isAnnotation() 判断该元素是否是注解类型
后面的方法需要分为两类Declared方法和非Declared方法.
1.getAnnotation(Class annotationClass) 返回指定标注类型的实例, 如果没有返回为null
2 getAnnotations() 返回该元素上的所有标注 如果没有返回一个长度为0的数组.
3.getAnnotationsByType(Class
annotationClass) 这个在上面的@Repeatable的例子中已经使用了, 专门获取重复注解的实例.
另外一个版本是Declared 版本, 即是
getDeclaredAnnotation(Class
annotationClass) getDeclaredAnnotations()getDeclaredAnnotationsByType(Class annotationClass) 这个版本的方法区别即是获取的注解全是直接注解, 即是没有@Inherited标注的注解, 父类上的@Inherited注解是无法通过这些方法获取到的.

你可能感兴趣的:(Java)