为什么要使用注解(annotation)?
在Java 应用,当需要创建描述符性质的类或接口时, 一旦 其中包含了重复性的工作, 可以考虑使用注解 来简化与自动化该过程.
Java 内置了三种注解, 称为标准注解:
@Override
@Deprecated
@SuppressWarnings
一. 定义注解
看一个例子 ,注解@Test的定义,
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}
可以看到, 注解的定义很像接口的定义. 注解也将会编译成class文件
1. 除了@符号外, @Test 的定义很像 一个空的接口, 定义注解时, 会需要一些元注解, 如 @Target 和 @Retention.
@Target 用来定义你的注解将应用于什么地方(如,一个方法或一个域)
@Rectetion 用来定义该注解在哪一个级别可用: 在源代码中(SOURCE), 类文件中(CLASS), 运行时(RUNTIME)
2. 在注解中,一般都会包含一些元素以表示某些值。 当分析处理注解时,程序或工具可以利用这些值。注解的元素看起来就像接口的方法, 唯一的区别是你可以为其指定默认值.
3. 没有元素的注解称为标记注解(marker annotation), 例如上例中的 @Test
二. 介绍 元注解
Java 目前有三种标准注解, 四种元注解。 元注解专职负责注解其他的注解:
@Target | 表示该注解可以用于什么地方, 可能的ElementType 参数包括: CONSTRUCTOR: 构造器的声明 FIELD: 域声明(包括 enum 实例) LOCAL_VARIABLE: 局部变量声明 METHOD: 方法声明 PACKAGE: 包声明 PARAMETER: 参数声明 TYPE: 类, 接口(包括注解类型), enum 声明 |
@Retention | 表示需要在什么级别保存该注解信息。 可选的RetentionPolicy 参数包括: SOURCE: 注解将被编译器丢弃 CLASS: 注解在class文件中可用, 但会被VM 丢弃 RUNTIME: VM 将在运行期也保留注解, 因此可以通过反射机制读取注解的信息. |
@Documented | 将此注解包含在Javadoc中。 |
@Inherited | 允许子类继承父类中的注解。 |
三. 编写注解处理器
如果没有读取注解的工具, 注解也不会比注释更有用; 使用注解的过程中, 很重要的一个部分就是创建与使用 注解处理器.
下面是一个简单的注解处理器, 我们用它来读取PasswordUtils 类,并使用反射机制查找 @UserCase 标记。 这里提供了一组id值,然后它会列出在PasswordUtils中找到的用例以及缺失的用例:
import java.lang.reflect.*;
import java.util.*;
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() +
" " + 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, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
这个程序用到了两个反射的方法: getDeclaredMethods() 和 getAnnotation() , 它们都属于AnnotatedElement 接口(Class, Method, Field 等类都实现了该接口)。 getAnnoation() 方法返回指定类型的注解对象, 在这里就是 UseCase. 如果被注解的方法上没有该类型的注解, 则返回 null 值,
然后通过调用id() 和 description() 从返回的UseCase 对象中提取元素的值 。其中encriptPassword() 在注解的时候没有指定description 的值, 因此在处理它对应的注解时,通过 description() 方法取得的是默认值 no description.import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description";
}
import java.util.List;
public class PasswordUtils {
@UseCase(id = 47, description ="密码必须包括至少一个数字")
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UseCase(id = 48)
public String encryptPassword(String password) {
return new StringBuilder(password).reverse().toString();
}
@UseCase(id = 49, description ="新的密码不能等于之前已使用的")
public boolean checkForNewPassword(List<String> prevPasswords, String password) {
return !prevPasswords.contains(password);
}
}
运行UserCaseTracket 类main方法,结果如下: