第二十章、注解
前言
注解Annotation:又叫元数据,JDK5引入的一种以通用格式为程序提供配置信息的方式。
使用注解Annotation可以使元数据写在程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Annotation的类型检查,使得在编译期间就可以排除语法错误。
注解使得我们能够以将由编译器来测试和验证的格式,存储有关程序的额外信息。
注解可以用来生成描述符文件,甚至是新的类定义,并且有助于减轻编写"样板"代码的负担。
JDK内置的3中Annotation
在JDK5中,内置了3个通用目的的注解Annotation,这三个内置的注解在java.lang包下:
@Override:这个注解常用在继承类或实现接口的子类方法上,表面该方法是子类覆盖父类的方法,该方法的方法签名要遵循覆盖方法的原则:即访问控制权限必能比父类更严格,不能比父类抛出更多的异常。
@Deprecated:这个注解告诉编译器该元素是过时的,即在目前的JDK版本中已经有新的元素代替该元素。
@SuppressWarnings:该注解关闭编译器中不合适的警告,即强行压制编译器的警告提示。
1. 基本语法
1.1 定义注解
标记注解(marker annotation):没有元素的注解,如@Test void test(){}
元注解(meta-annotation):在声明注解的时候往往需要使用@Target,@Retention等注解,这种注解被称为注解的注解(元数据注解),即是专门用于处理注解Annotation本身的。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{}
1.2 元注解
Java只内置三个标准注解(如上所述),和下列4个元注解:
@Target注解
用于指示注解所应用的目标程序元素种类,该注解常和ElementType枚举类型一起联合使用,ElementType枚举提供了java程序中声明的元素类型如下:
ANNOTATION_TYPE:注释类型声明。
CONSTRUCTOR:构造方法声明。
FIELD:字段声明(包括枚举常量)。
LOCAL_VARIABLE:局部变量声明。
METHOD:方法声明。
PACKAGE:包声明。
PARAMETER:参数声明。
TYPE::类,接口或枚举声明。
@Retention注解
该注解用于指示所定义的注解类型的注释在程序声明周期中得保留范围,该注解常和RetentionPolicy枚举联合使用。RetentionPolicy枚举常量定义了注解在代码中的保留策略
CLASS:编译器把注解记录在类文件中,但在运行时JVM不需要保留注解。
RUNTIME:编译器把注解记录在类文件中,在运行时JVM将保留注解,因此可以通过反射机制读取注解。
SOURCE:仅保留在源码中,编译器在编译时就要丢弃掉该注解。
@Documented
将此注解包含在Javadoc中
@Inherited
允许子类继承父类中的注解
2. 编写注解处理器
使用注解的时候,很重要的一部分就是创建&使用注解处理器。Java提供了一个外部工具apt用来解析带有注解的Java源代码包。
正常使用注解时,需要在注解中定义元素,用于接收程序设置的值,正常定义注解的例子如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase{
public int id();
public String description() default “no description”; //可以定义属性默认值
}
正常定义注解Annotation的方式类似定义接口,id和description是注解UseCase的属性,而不是方法,注解中不能定义方法只能定义属性。其中description属性有默认的值“no description“,即在使用时如果没有指定description的值,则程序使用其默认值。
上面UseCase注解用于跟踪方法的测试用例说明,使用上面注解的例子如下:
public class PasswordUtils{
@UseCase(id = 47, description = “Passwords must contain at least one numeric”)
public Boolean validatePassword(String password){
return (password.mathes(“\\w*\\d\\w*”));
}
@UseCase(id = 48)
public String encryptPassword(Srring password){
return new StringBuilder(password).reverse().toString();
}
@UseCase(id = 49, description = “New passwords can’t equal previously used ones”)
public Boolean checkForNewPassword(List<String> prevPasswords, String password){
return !prevPasswords.contains(password);
}
}
JDK5中提供了Annotation相关的API,结合使用java的反射机制可以实现自定义的Annotation注解处理器(JDK中也提供了使用APT,Annotationprocess tool方式处理注解,在后面会讲解),处理上述Annotation的例子如下:
public class UseCaseTracker{
public static void traceUseCases(List<Integer> useCases, Class<?> clazz){
//获取指定类中所有声明的方法
for(Method m : clazz.getDeclaredMethods()){
//获取方法上指定类型的注解
public class UseCaseTracker{
public static void traceUseCases(List<Integer> useCases, Class<?> clazz){
//获取指定类中所有声明的方法
for(Method m : clazz.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 ArrayLis<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
}
这个程序用到了两个反射的方法:getDeclaredMethods()和m.getAnnotation(UseCase.class):
getDeclaredMethods() 返回类声明的方法
m.getAnnotation(UseCase.class) 返回指定类型的注解对象。如果被注解的方法上没有该类型的注解,则返回null值。
2.1 Annotation注解元素
Annotation注解中的元素只能是下面的数据类型:
所有的基本类型(int,float,boolean等): java的8种基本类型,如int, boolean等等,如果可以自动装箱和拆箱,则可以使用对应的对象包装类型。
String
Class
enum
Annotation
上面类型的数组
除了上面这些类型以外,如果在注解中定义其他类型的数据,编译器将会报错。
2.2 默认值限制
注意:注解中的元素要么指定默认值,要么由使用的类赋值,如果即没有默认值,使用类也没有赋值的话,注解元素是不会像普通类成员变量一样给定默认值,即必须赋值或者显示指定默认值。默认值例子如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DefaultValue{
public int id() default -1;
public String description() default “”;
}
2.3 生成外部文件
Enterprise JavaBean这样的技术,每一个Bean都需要大量的接口和部署来描述文件,这些都属于样板文件。然而,如果我们使用注解的话,可以将所有信息都保存在javaBean中的源文件中。为此,我们需要一些新的注解,用以定义与Bean相连的数据库表的名字,以及与Bean属性管理的列的名字和SQL类型。
@Target(ElementType.TYPE)//该注解只能应用在类上
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable{//指定数据库名称
public String name() default “”;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints{//数据库约束
boolean primaryKey() default false;
boolean allowNull() default true;
boolean unique() default false;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString{//String类型数据
int value() default 0;
String name() default “”;
Constraints constraints() default @Constraints;//注解的属性元素也是注解
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger{//int类型数据
String name() default “”;
Constraints constraints() default @Constraints;
}
2.4 注解不支持继承
不能使用关键字extends 来继承某个@interface
3 使用apt处理注解
apt:注解处理工具,是Sun为了帮助注解的处理过程而提供的工具;
同javac一样,apt被设计为操作Java源文件,而不是编译后的类。
使用apt生成注解处理器时,我们无法使用Java的反射机制,因为我们操作的是源代码,额不是编译后的文件。
4. 将观察者模式用于apt
5. 基于注解的单元测试
单元测试:是对类中的每个方法提供一个或多个测试的一种实践,目的是为了有规律地测试一个类的各个方法是否具备正确的行为。
单元测试工具:Java中,最著名的单元测试工具JUnit。
5.1 将@Unit用于泛型
5.2 不需要任何“套件”
5.3 实现@Unit
5.4 移除测试代码
添加@Test注解,并使用Javassist工具类库将字节码工程,删除所有的@Test注解。(??)