Java注解

  Annotation 提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation 就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在 Annotation 的“name=value”对中。
  Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来取得注解里的元数据。
  Annotation 能被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,Annotation 不影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。如果希望让程序中 Annotation 在运行时起一定的作用,只有通过某种配套的工具对 Annotation 中的信息进行访问和处理,访问和处理 Annotation 的工具统称 APT(Annotation Processing Tool)。
1、基本 Annotation
  Annotation 必须使用工具来处理,工具负责提取 Annotation 里包含的元数据,工具还会根据这些元数据增加额外的功能。Java 提供了 5 个基本 Annotation:
  @Override
  @Deprecated
  @SupressWarnings
  @SafeVarargs
  @FunctionalInterface
  上面 5 个基本Annotation 中的@SafeVarargs 是 Java7 新增的、@FunctionalInterface 是 Java8 新增的。这 5 个基本的 Annotation 都定义在 java.lang 包下,可通过查阅 API 了解更多细节。
Java8 的函数式接口与@FunctionalInterface
  Java8 规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口。
  函数式接口就是为 Java8 的 Lambda 表达式准备的,Java8 允许使用 Lambda 表达式创建函数式接口的实例,因此 Java8 专门增加了@FunctionalInterface。
@FunctionalInterface
public interface FunInterface {
        static void Foo() {
                System.out.println("foo 类方法");}
    </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> bar() {
            System.out.println(</span>"bar默认方法"<span style="color: rgba(0, 0, 0, 1)">);
    }
    
    </span><span style="color: rgba(0, 0, 255, 1)">void</span> test();        <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">只定义一个抽象方法</span>
}
  @FunctionalInterface 只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法(抽象方法只能有一个,否则会报错),否则就会编译出错。
  @FunctionalInterface 只能修饰接口,不能修饰其他程序元素。
2、JDK 的元 Annotation
  JDK 除了在 java.lang 下提供了 5 个基本的Annotation 之外,还在 java.lang.annotation 包下提供了 6 个 Meta Annotation(元 Annotation),其中有 5 个元 Annotation 都用于修饰其他的 Annotation 定义。其中@Repeatable 专门用于定义 Java8 新增的重复注解。
 
使用@Inherited
  @Inherited 元Annotation 指定被它修饰的 Annotation 将具有继承性 -- 如果某个类使用了 @Xxx 注解(定义该 Annotation 时使用了 @Inherited 修饰)修饰,则其子类将自动被 @Xxx 修饰。
  下面使用@Inherited 元Annotation 修饰 @Inheritable 定义,则该 Annotation 将具有继承性。
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Inheritable {

}

  如果某个类使用了@Inheritable 修饰,则该类的子类将自动使用 @Inheritable 修饰。

@Inheritable
class Base
{

}

public class InheritableTest extends Base{

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) {
            System.out.println(InheritableTest.</span><span style="color: rgba(0, 0, 255, 1)">class</span>.isAnnotationPresent(Inheritable.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">));
    }

}

  Base 类使用了@Inheritable 修饰,而该Annotation 具有继承性,所以其子类也将自动使用 @Inheritable 修饰。运行上面程序,会看到输出:true.
  如果将 InheritableTest.java 程序中的粗体字代码注释掉或者删除,将会导致@Inheritable 不具有继承性。运行上面程序,将看到输出:false。
3、自定义 Annotation
  定义新的Annotation 类型使用@interface 关键字(在原有的 interface 关键字前增加 @符号)定义一个新的 Annotation 类型与定义一个接口非常像,如下代码可定义一个简单的 Annotation 类型。
//定义一个简单的 Annotation 类型
public @interface Test {

}

  定义了该Annotation 之后,可以在程序的任何地方使用该 Annotation,使用 Annotation 的语法非常类似于 public、final 这样的修饰符,通常可用于修饰程序中的类、方法、变量、接口等定义。通常会把 Annotation 放在所有修饰符之前,而且由于使用 Annotation 时可能还需要为成员变量指定值,因而 Annotation 的长度可能较长,所以通常把 Annotation 另放一行,如下程序所示:

@Test
public class MyClass
{...}

  Annotation 还可以带成员变量,Annotation 的成员变量在 Annotation 定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。如:

public @interface MyTag {
        //定义带两个成员变量的 Annotation
        //Annotation 中的成员变量以方法的形式来定义
        String name();
        int age();}

  一旦在Annotation 里定义了成员变量之后,使用该 Annotation 时就应该为该 Annotation 的成员变量指定值。如:

public class Test {
        //使用带成员变量的 Annotation 时,需要为成员变量赋值
        @MyTag(name="xx", age=6)
        public void info() {
    }

}

  也可以在定义Annotation 的成员变量时为其指定初始值(默认值),指定成员变量的初始值可使用 default 关键字。

public @interface MyTag {
        //定义带两个成员变量的 Annotation
        //Annotation 中的成员变量以方法的形式来定义
        String name() default "Jason";
        int age() default 26;
}
  根据Annotation 是否可以包含成员变量,可以把 Annotation 分为如下两类:
  标记Annotation:没有定义成员变量的 Annotation 类型被称为标记。这种 Annotation 仅利用自身的存在与否来提供信息,如@Override。
  元数据Annotation:包含成员变量的 Annotation,因为它们可以接受更多的元数据,所以也被称为元数据 Annotation。
4、提取 Annotation 信息
  使用Annotation 修饰了类、方法、成员变量等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的工具来提取并处理 Annotation 信息。
  Java 使用Annotation 接口来代表程序元素签名的注解,该接口是所有注解的父接口。
  java.lang.reflect 包下主要包含一些实现反射功能的工具类,从 Java5 开始,java.lang.reflect 包所提供的反射 API 增加了读取运行时Annotation 的能力。只有当定义 Annotation 时使用了@Retention(RetentionPolicy.RUNTIME) 修饰,该 Annotation 才会在运行时可见,JVM 才会在装载 *.class 文件时读取保存在 class 文件中的 Annotation。
  AnnotatedElement 接口是所有程序元素(如 Class、Method、Constructor 等)的父接口,所以程序通过反射获取了某个类的 AnnotatedElement 对象(如 Class、Method、Constructor 等)之后,程序就可以调用该对象的几个方法来访问 Annotation 信息。
5、使用 Annotation 的示例
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义一个标记注解,不包含任何成员变量,即不可传入元数据
public @interface Testable {

}

  @Retention 注解指定 Testable 注解可以保留到运行时(JVM 可以提取到该Annotation 的信息),而 @Target 注解指定 @Testable 只能修饰方法。

package com.turing.annotation.test;
public class MyTest {
        @Testable
        public static void m1() {
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m2() {
            
    }
    @Testable
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m3() {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> IllegalArgumentException("参数出错了!"<span style="color: rgba(0, 0, 0, 1)">);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m4() {
            
    }
    @Testable
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m5() {
            
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m6() {
            
    }
    @Testable
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m7() {
            </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> RuntimeException("程序业务出现异常!"<span style="color: rgba(0, 0, 0, 1)">);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> m8() {
            
    }

}

  仅仅使用注解来标记程序元素对程序是不会有任何影响的,这也是 Java 注解的一条重要原则。为了让程序中的这些注解起作用,接下来必须为这些注解提供一个注解处理工具。

import java.lang.reflect.Method;

public class ProcessorTest {

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> process(String clazz) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> SecurityException, ClassNotFoundException {
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> passed = 0<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 0, 255, 1)">int</span> failed = 0<span style="color: rgba(0, 0, 0, 1)">;
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 遍历clazz对应的类里的所有方法</span>
            <span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Method m : Class.forName(clazz).getMethods()) {
                    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果该方法使用了@Testable修饰</span>
                    <span style="color: rgba(0, 0, 255, 1)">if</span> (m.isAnnotationPresent(Testable.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">)) {
                            </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
                                    m.invoke(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
                                    passed</span>++<span style="color: rgba(0, 0, 0, 1)">;
                            } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex) {
                                    System.out.println(</span>"方法" + m + "运行失败,异常:" +<span style="color: rgba(0, 0, 0, 1)"> ex.getCause());
                                    failed</span>++<span style="color: rgba(0, 0, 0, 1)">;
                            }
                    }
            }
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 统计测试结果</span>

System.out.println(
"共运行了:" + (passed + failed) + "个方法,其中:\n" + "失败了:" + failed + "个,\n" + "成功了:" + passed + "个!");
}
}

  主程序:

public class RunTests {
        public static void main(String[] args) {
                try {
                        ProcessorTest.process("com.turing.annotation.test.MyTest");} catch (SecurityException | ClassNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();}
        }
}

Outputs:

方法 public static void com.turing.annotation.test.MyTest.m3() 运行失败,异常:java.lang.IllegalArgumentException: 参数出错了!
方法 public static void com.turing.annotation.test.MyTest.m7() 运行失败,异常:java.lang.RuntimeException: 程序业务出现异常!
共运行了: 4 个方法,其中:
失败了:2 个,
成功了: 2 个!