一、概述
1、注解(Annotations):也被称为元数据(matadata),为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍
后某个时刻非常方便地使用这些数据。
(1)定义注解并且将该注解应用于代码当中,就可以为该代码添加除了代码外的信息,并且这些信息会由编译器进行检查,以对其
提供编译期类型检查保护。这样,就得到了额外信息和源代码结合在一起的干净易读的代码;
(2)在稍后的某个时刻,可以通过编写注解处理器——利用这些注解提供的信息实现特定的功能;
(3)通过使用注解,我们可以将元数据保存在Java源代码中,并利用annotationAPI为自己的注解构造处理工具;
(4)我们可以想象的到,注解很可能引发Java编程体验的巨大改变。
2、注解是众多引入到JavaSE5中的重要的语言特性变化之一,并且是一项非常受欢迎的补充
(1)引入注解的原因:
(2)注解可以在代码中提供Java无法表达的、用来完成描述程序所需的信息。注解使得这些与程序有关的额外信息的加入不会导致
3、注解的常见用途
(1)注解可以用来生成描述符文件,甚至新类的定义,并且有助于减轻编写“样板”代码的负担;
(2)消除重复工作
(3)用于framework生成一些部署描述文件
二、注解的基本使用
1、JavaSE5中只内置了3种标准注解和4种元注解(在后续版本中添加了其它注解)
因此,大多数时候程序员需要定义自己的注解,并编写自己的处理器来处理它们。
(1)3种标准注解定义在java.lang中:
除了@SuppressWarnings有一个必须的String[]类型的value元素外,其它两个都是标记注解。
(2)4种元注解——专职负责注解其它注解,即用于修饰正在创建的自定义注解,位于java.lang.annotation,如下:
1)@Documented和@Inherited是标记注解(markerannotation)——没有元素的注解,因此在使用它们时无需指定元素;
2)@Target用来指定正在定义的注解可以修饰哪些API(例如:方法、域、类等)
使用@Target时必须指定一个ElementType[]类型的value元素,多个ElementType之间可以用逗号分隔开。
如果定义一个注解时,没有使用@Target进行修饰,那么:
If an @Target
meta-annotation is notpresent on an annotation type T
, thenan annotation of type T
may be writtenas a modifier for any declaration except a type parameterdeclaration.
例如:
package com.hemeng.study.chapter20.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义注解
* 看起来很像接口的定义
* @author hemeng
* 2017/01/05
*/
//使用了2个元注解
@Target(ElementType.METHOD) //指定该注解将应用于什么地方
@Retention(RetentionPolicy.RUNTIME) //指定该注解在哪一个级别可用
public @interface Test {
}
3)@Retention用来指定正在定义的注解在那一个级别可用
使用@Retention时必须指定一个RetentionPolicy类型的value元素。
如果定义一个注解时没有使用@Retention,那么该正在定义的注解将会被置为默认值:RetentionPolicy.CLASS
2、使用自定义注解的一般方式
(1)在一个java文件中借助元注解定义自己的注解;
(2)用自定义的注解修饰需要添加额外信息的代码;
(3)创建针对上述自定义注解的注解处理器,并借助apt等工具使注解处理器作用于被自定义注解修饰了的代码。
3、定义注解
(1)注解的定义看起来非常像接口的定义,主要区别:
1)用@interface替代了interface
2)方法声明在这里表示注解的元素,并且可以在方法声明的“;”之前通过default指定默认值;
3)一般需要用@Target和@Retention来修饰正在定义的注解。
(2)事实上,与其它任何接口一样,注解也会被编译成class文件;
(3)元素:在注解中,一般都会包含一些元素以表示某些值(除非是标记注解)。当分析处理注解时,程序或工具可以利用这些
值。
定义元素就像定义接口的方法,唯一的区别是你可以为其指定默认值
如果一个元素没有指定默认值,那么在使用该注解时将会被强制指定该元素的值;
如果一个元素的名字为“value”,并且在使用该注解的时候,只需要指定这一个元素的值,那么此时就无需使用名-值对的语法,而只
需在括号内给出value元素的所需的值即可;
(4)元素支持的类型:
(5)元素的默认值
(6)例子:
package com.hemeng.study.chapter20.definition;
/**
* 定义一个用于标注测试用例的注解
* 看起来很像接口的定义
* @author hemeng
* 2017/01/05
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//使用了2个元注解
@Target(ElementType.METHOD)//指定该注解将应用与什么地方
@Retention(RetentionPolicy.RUNTIME)//指定该注解在哪一个级别可用
public @interface UseCase {
//定义一些元素,供注解处理器等工具处理该注解时使用
// 定义元素就像定义接口的方法,唯一的区别是你可以为其指定默认值
public int id(); //不指定默认值
public String description() default "no description"; //指定默认值
}
4、使用注解
package com.hemeng.study.chapter20.definition;
import java.util.List;
/**
* 使用自定义的注解
* @author hemeng
* 2017/01/05
*/
public class PasswordUtils {
@UseCase(id = 47, description =
"Passwords must contain at least one numeric")
//如果没有为无默认值的元素指定值,编译器将会报错
public boolean validatePassword(String password) {
return (password.matches("\\w*\\d\\w*"));
}
@UseCase(id = 48) //description元素将使用默认值
public String encryptPassword(String 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);
}
}
5、编写注解处理器
(1)一个注解处理器一般是针对某一个注解编写的;
(2)获取到使用了该注解的类的Class对象后,通过Class对象提供的反射方法可以针对类、方法、域等元素尝试获取该注解的实
例;
(3)对获取到的实例,提取其中的元素信息。从而,根据这些元素信息,编写处理逻辑进行相应处理。
例如:
package com.hemeng.study.chapter20.definition;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 为自定义注解创建注解处理器
* @author hemeng
* 2017/01/05
*/
public class UseCaseTracker {
public static void trackUsecase(
List<Integer> useCases, Class<?> c1) {
//通过反射获取指定class的所有方法
for(Method m : c1.getDeclaredMethods()) {
//尝试通过反射获取方法上的UseCase类型的注解的实例
UseCase uc = m.getAnnotation(UseCase.class);
if(uc != null) {
//访问Usecase类型的注解的实例的元素
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);
trackUsecase(useCases, PasswordUtils.class);
}
}
运行结果:
Found Use Case: 49 New passwords can't equal previously used ones
Found Use Case: 47 Passwords must contain at least one numeric
Found Use Case: 48 no description
Warning: Missing use case-50
6、辅助开发注解处理器的工具
(1)书中提到的apt工具从JavaSE7开始已经deprecated了。取而代之的是javac和javax.annotation.processing、javax.lang.model
两种包下的API。
Note: The apt tool and itsassociated API contaiined in the pakcage com.sun.mirrorhave been deprecated since Java SE 7. Use the options available inthe javactool and the APIs contained in the packagesjavax.annotation.processing
and javax.lang.model
to process annotations.
(2)Javassist能够用来操作字节码,或许你也可以编写自己的字节码操作工具。
三、注解使用示例
1、数据库表和字段注解(P624)——相对简单
模拟ORM框架中的@Table和@Column注解,为使用了该注解的Java类生成对应的SQL建表语句
2、基于注解的单元测试(P635)——较复杂
实现了与JUnit具有类似功能的单元测试框架
3、使用JavaSE中的javac和javax.annotation.processing进行注解处理
http://hannesdorfmann.com/annotation-processing/annotationprocessing101(生成一组类的工厂类)
参考资料:
《Java编程思想(第4版)》