23: 注解翻译更新

This commit is contained in:
sjsdfg
2019-04-24 14:55:04 +08:00
parent 0e84f2b4fb
commit 023a9d88ba

View File

@@ -111,14 +111,178 @@ public class PasswordUtils {
你应该能够想象到如何使用这套工具来“勾勒”出将要建造的凶,然后在建造的过程中逐渐实现系统的各项功能。
### 元注解
Java 语言中目前有 5 种标准注解(前面介绍过),以及 5 种元注解。元注解用于注解其他的注解
| 注解 | 解释 |
| ----------- | ------------------------------------------------------------ |
| @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:<br/>**CONSTRUCTOR**:构造器的声明<br/>**FIELD**:字段声明(包括 enum 实例)<br/>**LOCAL_VARIABLE**:局部变量声明<br/>**METHOD**:方法声明<br/>**PACKAGE**:包声明<br/>**PARAMETER**:参数声明<br/>**TYPE**:类、接口(包括注解类型)或者 enum 声明 |
| @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:<br/>**SOURCE**:注解将被编译器丢弃<br/>**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃。<br/>**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<Integer> 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<Integer> 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 "";
}
```
这是一个在定义注解的习惯用法。
### 生成外部文件
<!-- Using javac to Process Annotations -->
## 使用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 类型都定义相应的注解。不过为为示例,两个元素足够了。
<!-- Annotation-Based Unit Testing -->
## 基于注解的单元测试