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类方法");
        }
        
        default void bar() {
                System.out.println("bar默认方法");
        }
        
        void test();        //只定义一个抽象方法
        
}
  @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{

        public static void main(String[] args) {
                System.out.println(InheritableTest.class.isAnnotationPresent(Inheritable.class));
        }

}
  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() {
                
        }
        public static void m2() {
                
        }
        @Testable
        public static void m3() {
                throw new IllegalArgumentException("参数出错了!");
        }
        public static void m4() {
                
        }
        @Testable
        public static void m5() {
                
        }
        public static void m6() {
                
        }
        @Testable
        public static void m7() {
                throw new RuntimeException("程序业务出现异常!");
        }
        public static void m8() {
                
        }
}

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

import java.lang.reflect.Method;

public class ProcessorTest {

        public static void process(String clazz) throws SecurityException, ClassNotFoundException {
                int passed = 0;
                int failed = 0;
                // 遍历clazz对应的类里的所有方法
                for (Method m : Class.forName(clazz).getMethods()) {
                        // 如果该方法使用了@Testable修饰
                        if (m.isAnnotationPresent(Testable.class)) {
                                try {
                                        m.invoke(null);
                                        passed++;
                                } catch (Exception ex) {
                                        System.out.println("方法" + m + "运行失败,异常:" + ex.getCause());
                                        failed++;
                                }
                        }
                }
                // 统计测试结果
                System.out.println(
                                "共运行了: " + (passed + failed) + "个方法,其中:
" + "失败了:" + failed + "个,
" + "成功了: " + 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个!
原文地址:https://www.cnblogs.com/ycyoes/p/6183141.html