From 023a9d88ba772affc259b9dac73f2fabb02647e3 Mon Sep 17 00:00:00 2001
From: sjsdfg <736777445@qq.com>
Date: Wed, 24 Apr 2019 14:55:04 +0800
Subject: [PATCH] =?UTF-8?q?23:=20=E6=B3=A8=E8=A7=A3=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=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 | 164 ++++++++++++++++++++++++++++++++++++
1 file changed, 164 insertions(+)
diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md
index 7d4d328..1c66fb0 100644
--- a/docs/book/23-Annotations.md
+++ b/docs/book/23-Annotations.md
@@ -111,14 +111,178 @@ public class PasswordUtils {
你应该能够想象到如何使用这套工具来“勾勒”出将要建造的凶,然后在建造的过程中逐渐实现系统的各项功能。
+### 元注解
+
+Java 语言中目前有 5 种标准注解(前面介绍过),以及 5 种元注解。元注解用于注解其他的注解
+
+| 注解 | 解释 |
+| ----------- | ------------------------------------------------------------ |
+| @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:
**CONSTRUCTOR**:构造器的声明
**FIELD**:字段声明(包括 enum 实例)
**LOCAL_VARIABLE**:局部变量声明
**METHOD**:方法声明
**PACKAGE**:包声明
**PARAMETER**:参数声明
**TYPE**:类、接口(包括注解类型)或者 enum 声明 |
+| @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:
**SOURCE**:注解将被编译器丢弃
**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃。
**RUNTIME**:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 |
+| @Documented | 将此注解保存在 Javadoc 中 |
+| @Interited | 允许子类继承父类的注解 |
+| @Repeatable | 允许一个注解可以被使用一次或者多次(Java 8)。 |
+
+大多数时候,程序员定义自己的注解,并编写自己的处理器来处理他们。
+
## 编写注解处理器
+如果没有用于读取注解的工具,那么注解不会比注释更有用。使用注解中一个很重要的部分就是,创建与使用注解处理器。Java 拓展了反射机制的 API 用于帮助你创造这类工具。同时他还提供了 javac 编译器钩子在编译时使用注解。
+
+下面是一个非常简单的注解处理器,我们用它来读取被注解的 **PasswordUtils** 类,并且使用反射机制来寻找 **@UseCase** 标记。给定一组 **id** 值,然后列出在 **PasswordUtils** 中找到的用例,以及缺失的用例。
+
+```java
+// annotations/UseCaseTracker.java
+import java.util.*;
+import java.util.stream.*;
+import java.lang.reflect.*;
+public class UseCaseTracker {
+ public static void
+ trackUseCases(List useCases, Class> cl) {
+ for(Method m : cl.getDeclaredMethods()) {
+ UseCase uc = m.getAnnotation(UseCase.class);
+ if(uc != null) {
+ System.out.println("Found Use Case " +
+ uc.id() + "\n " + uc.description());
+ useCases.remove(Integer.valueOf(uc.id()));
+ }
+ }
+ useCases.forEach(i ->
+ System.out.println("Missing use case " + i));
+ }
+ public static void main(String[] args) {
+ List useCases = IntStream.range(47, 51)
+ .boxed().collect(Collectors.toList());
+ trackUseCases(useCases, PasswordUtils.class);
+ }
+}
+```
+
+输出为:
+
+```java
+Found Use Case 48
+no description
+Found Use Case 47
+Passwords must contain at least one numeric
+Found Use Case 49
+New passwords can't equal previously used ones
+Missing use case 50
+```
+
+这个程序用了两个反射的方法:`getDeclaredMethods()` 和 `getAnnotation()`,它们都属于 **AnnotatedElement** 接口(**Class**,**Method** 与 **Field** 类都实现了该接口)。`getAnnotation()` 方法返回指定类型的注解对象,在本例中就是 “**UseCase**”。如果被注解的方法上没有该类型的注解,返回值就为 **null**。我们通过调用 `id()` 和 `description()` 方法来提取元素值。注意 `encryptPassword()` 方法在注解的时候没有指定 **description** 的值,因此处理器在处理它对应的注解时,通过 `description()` 取得的是默认值 “no description”。
+
+### 注解元素
+
+在 **UseCase.java** 中定义的 **@UseCase** 的标签包含 int 元素 **id** 和 String 元素 **description**。注解元素可用的类型如下所示:
+
+- 所有基本类型(int、float、boolean等)
+- String
+- Class
+- enum
+- Annotation
+- 以上类型的数组
+
+如果你使用了其他类型,编译器就会报错。注意,也不允许使用任何包装类型,但是由于自动装箱的存在,这不算是什么限制。注解也可以作为元素的类型。稍后你会看到,注解嵌套是一个非常有用的技巧。
+
+### 默认值限制
+
+编译器对于元素的默认值有些过于挑剔。首先,元素不能有不确定的值。也就是说,元素要么有默认值,要么就在使用注解时提供元素的值。
+
+这里有另外一个限制:任何非基本类型的元素, 无论是在源代码声明时还是在注解接口中定义默认值时,都不能使用 null 作为其值。这个限制使得处理器很难表现一个元素的存在或者缺失的状态,因为在每个注解的声明中,所有的元素都存在,并且具有相应的值。为了绕开这个约束,可以自定义一些特殊的值,比如空字符串或者负数用于表达某个元素不存在。
+
+```java
+// annotations/SimulatingNull.java
+import java.lang.annotation.*;
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SimulatingNull {
+ int id() default -1;
+ String description() default "";
+}
+```
+
+这是一个在定义注解的习惯用法。
+
+### 生成外部文件
+
+
+
## 使用javac处理注解
+当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。
+
+假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库名称、数据库列以及将 SQL 类型映射到属性的注解。
+
+以下是一个注解的定义,它告诉注解处理器应该创建一个数据库表:
+
+```java
+// annotations/database/DBTable.java
+package annotations.database;
+import java.lang.annotation.*;
+@Target(ElementType.TYPE) // Applies to classes only
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DBTable {
+ String name() default "";
+}
+```
+
+在 `@Target` 注解中指定的每一个 **ElementType** 就是一个约束,它告诉编译器,这个自定义的注解只能用于指定的类型。你可以指定 **enum ElementType** 中的一个值,或者以逗号分割的形式指定多个值。如果想要将注解应用于所有的 **ElementType**,那么可以省去 `@Target` 注解,但是这并不常见。
+
+注意 **@DBTable** 中有一个 `name()` 元素,该注解通过这个元素为处理器创建数据库时提供表的名字。
+
+如下是修饰字段的注解:
+
+```java
+// annotations/database/Constraints.java
+package annotations.database;
+import java.lang.annotation.*;
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Constraints {
+ boolean primaryKey() default false;
+ boolean allowNull() default true;
+ boolean unique() default false;
+}
+```
+
+```java
+// annotations/database/SQLString.java
+package annotations.database;
+import java.lang.annotation.*;
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SQLString {
+ int value() default 0;
+ String name() default "";
+ Constraints constraints() default @Constraints;
+}
+```
+
+```java
+// annotations/database/SQLInteger.java
+package annotations.database;
+import java.lang.annotation.*;
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SQLInteger {
+ String name() default "";
+ Constraints constraints() default @Constraints;
+}
+```
+
+**@Constraints** 注解允许处理器提供数据库表的元数据。**@Constraints** 代表了数据库通常提供的约束的一小部分,但是它索要表达的思想已经很清楚了。`primaryKey()`,`allowNull()` 和 `unique()` 元素明智的提供了默认值,从而使得在大多数情况下,该注解的使用者不需要输入太多东西。
+
+另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过为为示例,两个元素足够了。
+
+
+
+
+
## 基于注解的单元测试