From 2c1027942855a5128534c84db770ca381a3331c5 Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Fri, 26 Apr 2019 16:47:21 +0800 Subject: [PATCH] =?UTF-8?q?23=EF=BC=9A=E6=B3=A8=E8=A7=A3=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/23-Annotations.md | 163 ++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 90a5fdb..ccd8386 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -443,17 +443,180 @@ CREATE TABLE MEMBER( REFERENCE VARCHAR(30) PRIMARY KEY); ``` +`main()` 方法会循环处理命令行传入的每一个类名。每一个类都是用 ` forName()` 方法进行加载,并使用 `getAnnotation(DBTable.class)` 来检查该类是否带有 **@DBTable** 注解。如果存在,将表名存储起来。然后读取这个类的所有字段,并使用 `getDeclaredAnnotations()` 进行检查。这个方法返回一个包含特定字段上所有注解的数组。然后使用 **instanceof** 操作符判断这些注解是否是 **@SQLInteger** 或者 **@SQLString** 类型。如果是的话,在对应的处理块中将构造出相应的数据库列的字符串片段。注意,由于注解没有继承机制,如果要获取近似多台的行为,使用 `getDeclaredAnnotations()` 似乎是唯一的方式。 +嵌套的 **@Constraint** 注解被传递给 `getConstraints()`方法,并用它来构造一个包含 SQL 约束的 String 对象。 + +需要提醒的是,上面演示的技巧对于真实的对象/映射关系而言,是十分幼稚的。使用 **@DBTable** 的注解来获取表的的名称,这使得如果要修改表的名字,则迫使你重新编译 Java 代码。这种效果并不理想。现在已经有了很多可用的框架,用于将对象映射到数据库中,并且越来越多的框架开始使用注解了。 ## 使用javac处理注解 +通过 **javac**,你可以通过创建编译时(compile-time)注解处理器在 Java 源文件上使用注解,而不是编译之后的 class 文件。但是这里有一个重大限制:你不能通过处理器来改变源代码。唯一影响输出的方式就是创建新的文件。 +如果你的注解处理器创建了新的源文件,在新一轮处理中注解会检查源文件本身。工具在检测一轮之后持续循环,直到不再有新的源文件产生。然后它编译所有的源文件。 +每一个你编写的注解都需要处理器,但是 **javac** 可以非常容易的将多个注解处理器合并在一起。你可以指定多个需要处理的类,并且你可以添加监听器用于监听注解处理完成后接到通知。 +本节中的示例将帮助您开始学习,但如果您必须深入学习,请做好反复学习,大量访问 Google 和StackOverflow 的准备。 +### 最简单的处理器 +让我们开始定义我们能想到的最简单的处理器,只是为了编译和测试。如下是注解的定义: + +```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 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] : : ()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) +``` + +这给了你一些可以发现的东西,包括参数名和类型、返回值等。 + +### 更复杂的处理器