ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
### 最简单的处理器 让我们开始定义我们能想到的最简单的处理器,只是为了编译和测试。如下是注解的定义: ```java // annotations/simplest/Simple.java // A bare-bones annotation package annotations.simplest; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE, ElementType.PACKAGE, ElementType.FIELD, ElementType.LOCAL_VARIABLE}) public @interface Simple { String value() default "-default-"; } ``` **@Retention** 的参数现在为 **SOURCE**,这意味着注解不会再存留在编译后的代码。这在编译时处理注解是没有必要的,它只是指出,在这里,**javac** 是唯一有机会处理注解的代理。 **@Target** 声明了几乎所有的目标类型(除了 **PACKAGE**) ,同样是为了演示。下面是一个测试示例。 ```java // annotations/simplest/SimpleTest.java // Test the "Simple" annotation // {java annotations.simplest.SimpleTest} package annotations.simplest; @Simple public class SimpleTest { @Simple int i; @Simple public SimpleTest() {} @Simple public void foo() { System.out.println("SimpleTest.foo()"); } @Simple public void bar(String s, int i, float f) { System.out.println("SimpleTest.bar()"); } @Simple public static void main(String[] args) { @Simple SimpleTest st = new SimpleTest(); st.foo(); } } ``` 输出为: ```java SimpleTest.foo() ``` 在这里我们使用 **@Simple** 注解了所有 **@Target** 声明允许的地方。 **SimpleTest.java** 只需要 **Simple.java** 就可以编译成功。当我们编译的时候什么都没有发生。 **javac** 允许 **@Simple** 注解(只要它存在)在我们创建处理器并将其 hook 到编译器之前,不做任何事情。 如下是一个十分简单的处理器,其所作的事情就是把注解相关的信息打印出来: ```java // annotations/simplest/SimpleProcessor.java // A bare-bones annotation processor package annotations.simplest; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import java.util.*; @SupportedAnnotationTypes( "annotations.simplest.Simple") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class SimpleProcessor extends AbstractProcessor { @Override public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment env) { for(TypeElement t : annotations) System.out.println(t); for(Element el : env.getElementsAnnotatedWith(Simple.class)) display(el); return false; } private void display(Element el) { System.out.println("==== " + el + " ===="); System.out.println(el.getKind() + " : " + el.getModifiers() + " : " + el.getSimpleName() + " : " + el.asType()); if(el.getKind().equals(ElementKind.CLASS)) { TypeElement te = (TypeElement)el; System.out.println(te.getQualifiedName()); System.out.println(te.getSuperclass()); System.out.println(te.getEnclosedElements()); } if(el.getKind().equals(ElementKind.METHOD)) { ExecutableElement ex = (ExecutableElement)el; System.out.print(ex.getReturnType() + " "); System.out.print(ex.getSimpleName() + "("); System.out.println(ex.getParameters() + ")"); } } } ``` (旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的示例关于注解如何简化你的代码)。 你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。 **Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 动态向下转型(在编译期不进行检查)并不像是 Java 的做事方式,这非常不直观这也是为什么我从未想过要这样做事。相反,我花了好几天的时间,试图发现你应该如何访问这些信息,而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式,但在我看来是。 如果只是通过平常的方式来编译 **SimpleTest.java**,你不会得到任何结果。为了得到注解输出,你必须增加一个 **processor** 标志并且连接注解处理器类 ```shell javac -processor annotations.simplest.SimpleProcessor SimpleTest.java ``` 现在编译器有了输出 ```shell annotations.simplest.Simple ==== annotations.simplest.SimpleTest ==== CLASS : [public] : SimpleTest : annotations.simplest.SimpleTest annotations.simplest.SimpleTest java.lang.Object i,SimpleTest(),foo(),bar(java.lang.String,int,float),main(java.lang.String[]) ==== i ==== FIELD : [] : i : int ==== SimpleTest() ==== CONSTRUCTOR : [public] : <init> : ()void ==== foo() ==== METHOD : [public] : foo : ()void void foo() ==== bar(java.lang.String,int,float) ==== METHOD : [public] : bar : (java.lang.String,int,float)void void bar(s,i,f) ==== main(java.lang.String[]) ==== METHOD : [public, static] : main : (java.lang.String[])void void main(args) ``` 这给了你一些可以发现的东西,包括参数名和类型、返回值等。