:::tips 注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它是框架学习和设计者必须掌握的基础。 :::

注解基础

主要作用:

  1. 生成文档,通过代码里标识的元数据生成javadoc文档。
  2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

    常见分类:

  5. Java自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。

  6. 元注解,元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented,@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
  7. 自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。

    Java内置注解

  8. Java 1.5开始自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings:

  • @Override:表示当前的方法定义将覆盖父类中的方法
  • @Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
  • @SuppressWarnings:表示关闭编译器警告信息

    内置注解 - @Override

  1. 我们先来看一下这个注解类型的定义: ```java @Target(ElementType.METHOD)//修饰方法 @Retention(RetentionPolicy.SOURCE)//编译时有效 public @interface Override { }
  1. 2. 从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。
  2. <a name="t0m8y"></a>
  3. #### 内置注解 - @Deprecated
  4. 1. 这个注解的定义如下:
  5. ```java
  6. @Documented//文档化
  7. @Retention(RetentionPolicy.RUNTIME)//运行时有效
  8. @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
  9. //能够修饰构造方法、属性、局部变量、方法、包、参数、类型。
  10. public @interface Deprecated {
  11. }
  1. 从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

    内置注解 - @SuppressWarnings

  2. 这个注解我们也比较常用到,先来看下它的定义: ```java @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})//修饰类型 @Retention(RetentionPolicy.SOURCE)//编译时有效 public @interface SuppressWarnings { String[] value(); }


2. 它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息.
<a name="Wn2T6"></a>
### 元注解

1. 上述内置注解的定义中使用了一些元注解(注解类型进行注解的注解类),在JDK 1.5中提供了4个标准的元注解:@Target,@Retention,@Documented,@Inherited, 在JDK 1.8中提供了两个元注解 @Repeatable和@Native。
<a name="kX5Qv"></a>
#### 元注解 - @Target
:::tips
Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。
:::

1. Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的**取值范围定义在ElementType 枚举中**。
```java
public enum ElementType {

    TYPE, // 类、接口、枚举类

    FIELD, // 成员变量(包括:枚举常量)

    METHOD, // 成员方法

    PARAMETER, // 方法参数

    CONSTRUCTOR, // 构造方法

    LOCAL_VARIABLE, // 局部变量

    ANNOTATION_TYPE, // 注解类

    PACKAGE, // 可用于修饰:包

    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增

    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增

}

元注解 - @Retention & @RetentionTarget

:::tips Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。 :::

  1. Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。 ```java public enum RetentionPolicy {

    SOURCE, // 源文件保留 CLASS, // 编译期保留,默认值 RUNTIME // 运行期保留,可通过反射去获取注解信息 }


2. 为了验证应用了这三种策略的注解类有何区别,分别使用三种策略各定义一个注解类做测试。
```java
public class RetentionTest {

    @SourcePolicy
    public void sourcePolicy() {
    }

    @ClassPolicy
    public void classPolicy() {
    }

    @RuntimePolicy
    public void runtimePolicy() {
    }
}
  1. 通过执行 javap -verbose RetentionTest命令获取到的RetentionTest 的 class 字节码内容如下。 ```java { public retention.RetentionTest(); flags: 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 3: 0
    

    public void sourcePolicy(); flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1

      0: return
    

    LineNumberTable:

     line 7: 0
    

    public void classPolicy(); flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1

      0: return
    

    LineNumberTable:

     line 11: 0
    

    RuntimeInvisibleAnnotations: 0: #11()

    public void runtimePolicy(); flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1

      0: return
    

    LineNumberTable:

     line 15: 0
    

    RuntimeVisibleAnnotations: 0: #14() }


4. 从 RetentionTest 的字节码内容我们可以得出以下两点结论:
- 编译器并没有记录下 sourcePolicy() 方法的注解信息;
- 编译器分别使用了 RuntimeInvisibleAnnotations 和 RuntimeVisibleAnnotations 属性去记录了classPolicy()方法 和 runtimePolicy()方法 的注解信息;
<a name="LeU9h"></a>
#### 元注解 - @Documented
:::tips
Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
:::

1. 以下代码在使用Javadoc工具可以生成@TestDocAnnotation注解信息。
```java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestDocAnnotation {
    public String value() default "default";
}


@TestDocAnnotation("myMethodDoc")
public void testDoc() {
}

元注解 - @Inherited

:::tips Inherited注解的作用:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。 :::

  1. 注解测试:
  • 定义@Inherited注解: ```java @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) public @interface TestInheritedAnnotation { String [] values(); int number(); }

- 使用这个注解
```java
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {
}

class Student extends Person{
    @Test
    public void test(){
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.toString());
        }
    }
}
  • 输出 ```java xxxxxxx.TestInheritedAnnotation(values=[value], number=10)

2. 即使Student类没有显示地被注解@TestInheritedAnnotation,但是它的父类Person被注解,而且@TestInheritedAnnotation被@Inherited注解,因此Student类自动有了该注解。
<a name="LbJeU"></a>
#### 元注解 - @Repeatable 
:::tips
允许在同一申明类型(类,属性,或方法)的多次使用同一个注解
:::

1. 重复注解
```java
@Repeatable(Authorities.class)
public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseNewVersion {
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}
  1. 不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。

    元注解 - @Native

  2. 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可

    注解与反射接口

    :::tips 定义注解后,如何获取注解中的内容呢?反射包java.lang.reflect下的AnnotatedElement接口提供这些方法。这里注意:只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。 ::: ……

    自定义注解

  • 定义自己的注解 ```java package com.pdai.java.annotation;

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

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyMethodAnnotation {

public String title() default "";

public String description() default "";

}


- 使用注解
```java
package com.pdai.java.annotation;

import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class TestMethodAnnotation {

    @Override
    @MyMethodAnnotation(title = "toStringMethod", description = "override toString method")
    public String toString() {
        return "Override toString method";
    }

    @Deprecated
    @MyMethodAnnotation(title = "old static method", description = "deprecated old static method")
    public static void oldMethod() {
        System.out.println("old method, don't use it.");
    }

    @SuppressWarnings({"unchecked", "deprecation"})
    @MyMethodAnnotation(title = "test method", description = "suppress warning static method")
    public static void genericsTest() throws FileNotFoundException {
        List l = new ArrayList();
        l.add("abc");
        oldMethod();
    }
}
  • 用反射接口获取注解信息
  • 在TestMethodAnnotation中添加Main方法进行测试: ```java public static void main(String[] args) { try {

      // 获取所有methods
      Method[] methods = TestMethodAnnotation.class.getClassLoader()
              .loadClass(("com.pdai.java.annotation.TestMethodAnnotation"))
              .getMethods();
    
      // 遍历
      for (Method method : methods) {
          // 方法上是否有MyMethodAnnotation注解
          if (method.isAnnotationPresent(MyMethodAnnotation.class)) {
              try {
                  // 获取并遍历方法上的所有注解
                  for (Annotation anno : method.getDeclaredAnnotations()) {
                      System.out.println("Annotation in Method '"
                              + method + "' : " + anno);
                  }
    
                  // 获取MyMethodAnnotation对象信息
                  MyMethodAnnotation methodAnno = method
                          .getAnnotation(MyMethodAnnotation.class);
    
                  System.out.println(methodAnno.title());
    
              } catch (Throwable ex) {
                  ex.printStackTrace();
              }
          }
      }
    

    } catch (SecurityException | ClassNotFoundException e) {

      e.printStackTrace();
    

    } }


- 测试的输出
```java
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @java.lang.Deprecated()
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @com.pdai.java.annotation.MyMethodAnnotation(title=old static method, description=deprecated old static method)
old static method
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.genericsTest() throws java.io.FileNotFoundException' : @com.pdai.java.annotation.MyMethodAnnotation(title=test method, description=suppress warning static method)
test method
Annotation in Method 'public java.lang.String com.pdai.java.annotation.TestMethodAnnotation.toString()' : @com.pdai.java.annotation.MyMethodAnnotation(title=toStringMethod, description=override toString method)
toStringMethod

深入理解注解

……