程序员

Java注解(annotation)机制

2018-12-04  本文已影响215人  juconcurrent

前言

jdk1.5引入了注解机制Annotation),用于对java里面的元素(如:Class、Method、Field等等)进行标记。同时,java的反射类库也加入了对Annotation的支持,因此我们可以利用反射来对特殊的Annotation进行特殊的处理,增强代码的语义。

本文主要是对Annotation的语法Annotation的用法进行分析阐述。然后对一些java自带的、常用的Annotation进行说明,加深读者的理解。

整体结构

借用网上的一张图,来说明整体结构。

Annotation整体结构图

通过这张图我们看到下面的信息

  1. 一个Annotation是一个接口
  2. 一个Annotation和一个RetentionPolicy关联
  3. 一个Annotation和多个ElementType关联
  4. Annotation可以有很多实现,包括java自带的@Override@Deprecated@Inherited等等,用户也可以自己定义

为了更好地理解Annotation,我们将Annotation整体结构拆分为左右两个部分来讲解。

Annotation的组成

先来看看整体结构的左边部分

整体结构左边部分

Annotation关联了3个重要的类,分别为AnnotationElementTypeRetentionPolicy,这3个类所在的包为java.lang.annotation,下面我们来看看java里面的定义。

1. Annotation接口

public interface Annotation {
    boolean equals(Object obj);
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

2. ElementType枚举

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE, // 类、接口(包括annotation类型)或者枚举声明

    /** 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, // annotation类型声明

    /** Package declaration */
    PACKAGE, // 包声明

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER, // 类型参数声明(泛型的类型参数)

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

3. RetentionPolicy枚举

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE, // 仅用于编译期间,编译完成之后,该annotation就无用了

    /**
     * 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, // 编译器将该annotation编译进class里面,但vm运行期间不会保留,默认行为

    /**
     * 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 // 编译器将该annotation编译近class,同时vm运行期间也可以对此annotation使用反射读取
}

接下来我们对这3个类做一下总结

  1. Annotation是一个接口,一个Annotation包含一个RetentionPolicy多个ElementType
  2. ElementType是类型属性,一种类型可以简单地看成一种用途,每个Annotation可以有多种用途
  3. RetentionPolicy是策略属性,共有3种策略,每个Annotation可选择一种策略,默认为CLASS策略
    • SOURCE,仅用于编译期间,编译完成之后,该Annotation就无用了。@Override就属于这种策略,仅仅在编译期检查子类是否可覆盖父类
    • CLASS,编译期将该Annotation编译进class里面,但vm运行期间不会保留,默认行为
    • RUNTIME,编译期将该Annotation编译进class,同时vm运行期间也可以对此Annotation使用反射读取

通用定义

我们来看看Annotation的通用定义,先来写一个自定义Annotation

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}

常用Annotation

我们再来看整体结构的右边部分,我们看到很多我们经常接触到的Annotation。我们会逐一来分析一下。

整体结构的右边部分

【元注解】:网上有元注解一说,其实注解上面的注解叫做元注解。当我们通过@Target(ElementType.ANNOTATION_TYPE)来修饰一个Annotation的时候,就表示该Annotation是一个元注解。

在jdk里面,有一些Annotation是经常用到的,为了加深我们对Annotation的理解,我们需要对这些Annotation进行分析。

1. @Inherited

我们写一个例子就能很容易看出@Inherited的作用了。

public class InheritedTest {

    @Documented
    @Inherited
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation {
    }

    @MyAnnotation class Father {
    }

    class Child extends Father {
    }

    public static void main(String[] args) {
        MyAnnotation[] annotations = Child.class.getAnnotationsByType(MyAnnotation.class);
        System.out.println("annotations length: " + annotations.length);
        for (MyAnnotation annotation : annotations) {
            System.out.println(annotation);
        }
    }
}

Annotation@Inherited修饰时,运行结果如下:

使用@Inherited的效果

Annotation不用@Inherited修饰时,运行结果如下:

不使用@Inherited的效果

2. @Retention

  1. RetentionPolicy.SOURCE类型的class结构和运行效果
class结构图 运行效果图
  1. RetentionPolicy.CLASS类型的class结构和运行效果
    class结构图
运行效果图
  1. RetentionPolicy.RUNTIME类型的class结构和运行效果
    class结构图
运行效果图

3. @Target

ElementType.TYPE修饰的Annotation不能作用于字段和方法,在IntellijIdea里面直接就有错误提示,同时编译的时候也会出错。

IDEA警告 编译错误

4. @Deprecated

使用@Deprecated修饰的类或方法,编译不会出错,运行也不会出错,但是会给出警告。

IDEA警告

5. @Override

IDEA警告 编译出错

Child.sayHello2()虽然会有IDEA警告,但是不会编译出错;Child.sayHello3()因为使用了@Override修饰,但是在父类里面并没有sayHello3()这个方法,所以会编译出错;Child.sayHello1()属于正常使用。

6. @SuppressWarnings

@SuppressWarnings可用于消除警告,可以消除哪些情况下的警告呢,我们举一个例子。

例子图

在这个例子里面

  1. 我们使用了已经废弃的java.util.Date构造方法public Date(int year, int month, int date),所以在编译的时候会出现编译警告。
  2. 因为泛型擦除的原因,往指定泛型插入非泛型类型时会出现警告。
  3. 通过使用@SuppressWarnings,我们可以消除编译期警告

Annotation的作用

我们总结一下Annotation的作用,大概有下面这几种

  1. 编译检查,jdk自带的@SuppressWarnings@Deprecated@Override都有编译检查的功能
  2. 在反射中使用Annotation,这在很多的框架代码里面大量出现,比如Spring、Mybatis、Hibernate等等
  3. 根据Annotation生成帮助文档,通过使用@Documented,我们可以将Annotation也生成到javadoc里面
  4. 能够帮忙查看查看代码,比如通过使用@Override@Deprecated,我们很容易知道我们的集成层级、废弃状态等等

拾遗1 - @SuppressWarnings可以消除哪些警告

不同的编译器,可以消除的警告会有所不同,如果我们使用的是javac编译器,那么我们可以通过命令javac -X来列出所有支持的可以消除的警告。常用的有下面这些

拾遗2 - Annotation中的value和default

Annotation的成员变量里面,value很多时候可以简化我们对Annotation的使用。

  1. 当我们使用Annotation的时候,如果只有一个value需要传入的时候,我们省略掉value,即:Element
  2. value声明为数组,使用时需要用大括号括起来+逗号分隔,如{"FirstElement", "SecondElement"}
  3. value声明为数组,使用时只有一个元素时,可当成单元素使用,即:"Element"
public class ValueTest {

    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME) @interface OneElement {
        String value();
    }

    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME) @interface MultiElement {
        String[] value();
    }

    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME) @interface MultiButOnlyInputOneElement {
        String[] value();
    }

    @OneElement("abc")
    @MultiElement({"abc", "cba"})
    @MultiButOnlyInputOneElement("abc")
    class User {
    }
}

同时,在定义Annotation成员变量的时候,我们可以使用default来设置默认值,使用时如果该成员变量的值和默认值相同,则可省略此成员变量的值传入。

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation {
    String value() default "is null";
}

总结

本文,我们首先给出了Annotation的整体结构图,然后分析了Annotation的语法和用法,最后我们给出了一些例子来说明了jdk自带的Annotation的用法,并总结了Annotation的使用场景。同时,我们通过对一些技巧性使用的补充,加深了我们对Annotation的印象。

参考链接

上一篇 下一篇

猜你喜欢

热点阅读