注解.png

1. 概念

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

注解主要有如下三个作用:

  • 编写文档:通过代码里标识的元数据生成文档,如使用javadoc命令生成文档doc文档
  • 代码分析:通过代码里标识的元数据对代码进行分析,如使用反射
  • 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查,如@Override

简单来说,注解就是给计算机看的一种用于说明程序的东西,主要是起到一种标识的作用。

2. 分类

按运行机制分类:

  • 源码注解
  • 编译时注解
  • 运行时注解

按来源分类:

  • Java内置注解
  • 第三方注解
  • 自定义注解

3. Java内置注解

Java本身定义了7个注解,其中@Override@Deprecated@SuppressWarnings位于java.lang中,下面称为内置注解;另外4个位于java.lang.annotation中,将其称为元注解。

2.1 内置注解

  • @Override:这个注解在前面类的使用中已经见过很多次了,例如,如果想要重写父类中定义的方法,子类重写的方法上就可以添加@Override;以及任何类重写Object类中的toString()equals()等方法时,IDEA都会自动的在方法上添加注解。因此,它主要用于编译时检查添加注解的方法是否是重写方法,当它的父类或实现的接口中没有此方法时,编译就会自动报错java @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }

  • @Deprecated:它用于标记过时的方法,注意标记为过时的方法,只是说它已经有了更好替代,方法仍可用只是不推荐。例如Date类中就存在大量被@Deprecated标记的方法,因为Calendar类中有其对应的替代方法。java @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }

  • @SuppressWarnings:它用于指示编译器去忽略注解中声明的警告。例如,当类中定义的方法没有使用时,编辑器会自动显式警告,如果想要忽略警告信息们可以使用该注解java @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }

使用示例:

  1. @SuppressWarnings("all")
  2. public class AnnotationTest {
  3. @Override
  4. public String toString() {
  5. return "AnnotationTest{}";
  6. }
  7. @Deprecated
  8. public void show(){
  9. }
  10. // show()的更新版
  11. public void newShow(){
  12. }
  13. }

2.2 元注解

元注解和所有的元xx含义是类似的,它就是用来注解其他注解的注解。Java内置了如下4个元注解:

  • @Retention:它用来标识注解应该保存于Java程序三个阶段的哪一个中,只在源代码中、写入.class文件中还是运行时通过反射访问。
    @Retention的源码实现为:```java package java.lang.annotation;

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

  1. <br />它只有一个成员变量`value()`,类型为`RetentionPolicy`。我们继续看一下`RetentionPolicy`的源码:```java
  2. package java.lang.annotation;
  3. public enum RetentionPolicy {
  4. // Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息
  5. SOURCE,
  6. // 编译器将Annotation存储于类对应的.class文件中。默认行为
  7. CLASS,
  8. // 编译器将Annotation存储于class文件中,并且在运行时可由JVM读入
  9. RUNTIME
  10. }


从源码中可以看出,RetentionPolicy是一个枚举(enum)类型,其中包含有SOURCECLASSRUNTIME三个值,它们分别对应了Java程序的三个阶段

  • @Documented:它用来标记注解是否应包含到文档中,主要用于使用javadoc生成文档时是否会显示注解
    注解的源码实现为:```java package java.lang.annotation;

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

  1. <br />它本身没有包含成员变量,一般在使用中它可有可无,只有在有需要生成javadoc文档时有用。
  2. - `@Target`:它用来标记**注解应是那种Java成员**,注解的源码实现如下:```java
  3. @Documented
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Target(ElementType.ANNOTATION_TYPE)
  6. public @interface Target {
  7. ElementType[] value();
  8. }


可以看到@Target中只有一个类型为ElementType[]类型的成员变量value(),接下来再看一下ElementType的源码:```java public enum ElementType { TYPE,

  1. FIELD,
  2. METHOD,
  3. PARAMETER,
  4. CONSTRUCTOR,
  5. LOCAL_VARIABLE,
  6. ANNOTATION_TYPE,
  7. PACKAGE,
  8. TYPE_PARAMETER,
  9. TYPE_USE

}

<br />`ElementType`同样是一个枚举类型,其中包含10个值,它们分别用于标记注解能用来修饰什么类型的成员:
   - TYPE:类、接口或枚举声明
   - FIELD:字段声明
   - METHOD:方法声明
   - PARAMETER:参数生命
   - CONSTRUCTOR:构造方法声明
   - LOCAL_VARIABLE:局部变量声明
   - ANNOTATION_TYPE:注解类型声明
   - PACKAGE:包声明
   - TYPE_PARAMETER:Type参数声明
   - TYPE_USE:类型使用声明
- `@Inherited`:它用来标记**注解是继承自哪个注解类**,源码实现为:```java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}


@Inherited的实现中同样没有成员变量,只是起来一个简单的标识作用。

4. 自定义注解

从前面Java内置注解的源码实现中,我们对于注解的定义有了初步的感受。如果用户想要自定义注解,注解的通用定义一般如下所示:

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    // 成员变量
}

注意事项:

  • @interface是定义注解的关键字,当使用它定义注解时,表示该注解实现了java.lang.annotation.Annotation接口,接口的实现由编译器完成,同时该注解不能再继承其他注解或接口
  • 成员变量的类型是有限的,合法的类型包括:
    • 八大基本数据类型
    • String
    • 枚举
    • 注解
    • 以上类型的数组
  • 当注解中只有一个成员变量时,变量名必须是value(),注解使用时可以忽略成员名和=
  • 没有成员变量的注解称为标识注解
  • 可以使用default关键字给成员变量设置默认初始化值,使用注解时就可以不对成员变量赋值
  • 注解使用时,如果成员变量是数组,那么赋值时使用{}包含,如果只有一个值,则可忽略{}

假设我们定义一个注解@Description,它只有一个String类型的成员变量value()

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented

public @interface Description {
    String value();
}

然后定义一个接口,接口中有一些抽象方法

public interface People {
    String name();
    int age();
    void work();
}

接着定义接口的实现类,并在类中使用自定义的注解

@Description("class annotation...")
public class Employee implements People{
    @Override
    public String name() {
        return null;
    }

    @Override
    @Description("method annotation...")
    public int age() {
        return 0;
    }

    @Override
    public void work() {

    }
}

由于注解中只有一个String类型的成员变量,因此这里简单的传入一个字符串,用来标识该注解用在哪里。定义好类之后,我们需要创建一个包含main()的类来使用Employee,定义如下:

import java.lang.reflect.Method;

public class AnnotationDemo {
    public static void main(String[] args) throws Exception{
        // 获取Class类对象
        Class<?> aClass = Class.forName("Annotation.Employee");
        // 判断类是否使用了注解
        if (aClass.isAnnotationPresent(Description.class)){
            // 获取使用的注解实例
            Description annotation = aClass.getAnnotation(Description.class);
            System.out.println(annotation.value());  //class annotation...
        }

        // 获取类对象的成员方法
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            // 判断方法是否使用了注解
            if (method.isAnnotationPresent(Description.class)){
                // 获取使用的注解实例
                Description annotation = method.getAnnotation(Description.class);
                System.out.println(annotation.value());  // method annotation...
            }
        }
    }
}

5. 使用案例

在前面反射的部分,我们使用了反射来运行配置文件内容,同样的功能也可以使用今天学习的注解来实现。首先定义Porp注解,其中包含className()methodName()两个String类型的成员变量,它和前面的配置文件是对应的。

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Prop {
    String className();
    String methodName();
}

然后使用同样的Person类:

package Annotation;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school='" + school + '\'' +
                '}';
    }

    public void say(){
        System.out.println("say hello...");
    }
}

最后编写测试类:

import java.lang.reflect.Method;

@Prop(className = "Annotation.Person", methodName = "say")
public class PropDemo {
    public static void main(String[] args) throws Exception {
        // 获取Class类对象
        Class<PropDemo> c = PropDemo.class;
        Prop annotation = c.getAnnotation(Prop.class);
        String className = annotation.className();
        String methodName = annotation.methodName();

        Class<?> aClass = Class.forName(className);
        // 获取方法对象
        Method method = aClass.getMethod(methodName);
        // 创建对象
        Object o = aClass.newInstance();
        // 执行方法
        method.invoke(o); // say hello...

    }
}

可以看出使用注解不仅可以实现同样的功能,而且实现更加的优雅,代码书写更简洁。

6. 参考

框架开发之Java注解的妙用 Java 注解(Annotation)