JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统简友广场读书

Java编程思想笔记七:注解

2022-07-27  本文已影响0人  红薯的Java私房菜
7.注解.png

注解(也称元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。

Java SE5 内置了三种注解,定义在 java.lang 中:

1.使用注解

使用注解很简单,只需在要使用注解的元素前加 @ 后面跟要加的注解即可。例如使用 @Test 注解修饰 testExecute() 方法让其可运行:

public class TestTable {
    @Test
    void testExecute() { System.out.println("Executing..."); }
}

2.定义注解

我们除了可以使用 Java 内置的注解,还可以自定义注解。Java 提供了四种元注解专职负责注解其他的注解:

注解 描述
@Target 用来约束注解可以应用的地方(如方法、类或字段),可选的 ElementType 参数包括:
CONSTRUCTOR:构造函数声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMETER:参数声明
TYPE:类、接口(包括注解类型)或 enum 声明
TYPE_PARAMETER:Java8新增,类型参数
TYPE_USE:Java8新增,标注任意类型(不包括class)
@Retention 表示注解信息的保存级别,可选的 RetentionPolicy 参数包括:
SOURCE:注解将被编译器丢弃。
CLASS:注解在class文件中可用,但会被VM丢弃。
VM将在运行期也会保留注解,可通过反射机制读取注解的信息。
@Decumented 将此注解包含在 Javadoc 中
@Inherited 允许子类继承父类中的注解
@Repeatable Java 8新增的元注解,表示在同一个位置重复相同的注解

我们可以自定义一个 @UseCase 注解:

//: annotations/UseCase.java
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
    public int id();
    public String description() default "no description";
}

定义 PasswordUtils 类使用 @UseCase 注解:

public class PasswordUtils {
    @UseCase(id = 47, description = "Password must be number.")
    public boolean validatePassword(String password) {
        return (password.matches("/^[0-9]*$/g"));
    }
}

使用自定义注解时需要注意 4 点

  1. 注解元素可用类型有限
    @UseCase 注解包含了两个元素:int 类型的 id 和 String 类型的 description。注解元素只支持一下几种类型:
  1. 使用注解时注解元素必须有默认值
    注解的所有元素要么具有默认值,要么在使用时提供元素的值。此外,对于非基本类型的元素,无论是在源代码中声明时,还是在注解接口中定义默认值时,都不能以 null 作为元素的值。

  2. 使用快捷方式设置元素的默认值
    当注解中只有一个元素需要赋值时,无需使用元素名-值的这种语法,只需在括号内给出该元素的值即可。例如,PasswordUtils 中使用注解 @UseCase 时,description 用默认值即可,那只有 id 需要赋值,则可以使用快捷方式:

  3. 注解不支持继承,不能使用 extends 来继承某个 @interface

public class PasswordUtils {
    @UseCase(47)    // 快捷方式赋值:id = 47,description 使用默认值
    public boolean validatePassword(String password) {
        return (password.matches("/^[0-9]*$/g"));
    }
}

3.编写注解处理器

大多时候,我们除了自定义注解,还需要编写自己的注解处理器来处理它们,这就需要用到反射机制。我们写一个简单的注解处理器来处理 PasswordUtils 类中的 @UseCase 注解:

public class UseCaseTracker {
    public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
        for (Method m : cl.getDeclaredMethods()) {          // 反射:获取对象的所有方法
            UseCase uc = m.getAnnotation(UseCase.class)     // 反射:获取方法上指定的注解(@UseCase)
            if (uc != null) {
                System.out.println("Found Use Case:" + uc.id() + " " + uc.description())
                useCases.remove(new Integer(uc.id()));
            }
        }
        for (int i : useCases) {
            System.out.println("Warning: Missing use case-" + i);
        }
    }

    public static void main(String[] args) {
        List<Integer> useCases = new ArrayList<Integer>();
        Collections.addAll(useCases, 47, 48, 49);
        trackUseCases(useCases, PasswordUtils.class);
    }
}

4.使用 apt 处理注解

注解处理工具 apt 是 Sun 为了帮助注解的处理过程而提供的工具。

与 javac 一样,apt 被设计为操作 Java 源文件,而不是编译后的类。apt 会在处理完源文件后编译它们,如果在系统构建过程中自动创建了新的源文件,该文件会在新一轮的注解处理过程中接受检查,apt 会一轮一轮地处理,直到不再有新的源文件产生为止,然后它再编译所有文件。

我们自定义的每个注解都需要自己的处理器,apt 工具能够很容易地将多个注解处理器组合在一起。这样,我们就可以指定多个要处理的类,比自己去遍历所有的类文件简单的多。

通过使用 AnnotationProcessorFactory apt 能够为每一个它发现的注解生成一个正确的注解处理器。在使用 apt 生成注解处理器时,无法使用 Java 的反射机制,因为我们操作的时源代码,而不是编译后的类。使用 mirror API 可以解决这个问题,它使我们能够在未经编译的源代码中查看方法、域以及类型。

上一篇下一篇

猜你喜欢

热点阅读