- 许多Api需要相当数量的样板代码。比如,为了写一个JAX-RPC网络服务。你必须提供一个成对的接口和实现,如果程序用注解装饰来表明哪个方法是远程可调用的那么这个样板代码就可以被一个工具自动生成。
- 其他API需要“副文件”同时被维护。例如,JavaBeans需要一个BeanInfo类来和这个Bean同时维护,企业级的JavaBeans (EJB)需要一个部署描述符。如果通过注解用程序本身来维护这些副文件那么就会更方便也不容易出现错误。
- Java平台总是会有各种各样的特别的注解机制。比如短暂性修饰符就是一个特殊的注解表明一个域应该被序列化子系统忽略,@deprecated javadoc标记也是一个特殊的注解表明方法不应该在被使用了。Java平台提供了一些通用的目标注解(也就是元注解)机制允许你定义和使用你自己的注解类型。这个机制由一个声明注解类型语法和一个使用注解类型注解的语法组成。读取注解的API和注解处理由javac 工具提供支持。注释由类文件表示。
- 注解不会直接影响程序的语义,但是他们会影响程序被工具和库处理的方式,进而反过来影响运行程序的语义。注解可以从源文件读出类文件读出或在运行时反射。
- 注解补充了Javadoc标记。一般来说如果标记目的是影响或处理文档,它很可能是一个javadoc标记,否则就是一个注解。
- 一般的应用程序开发者将永远不必定义一个注解类型,但是定义一个注解也不是很困难。注解类型的定义类似于普通的接口定义。把一个@符号放在interface关键词之前,里面的每一个方法声明定义了这个注解类型的元素。方法声明必须不能有任何参数也不能有抛出语句。返回类型限制在原始类型, String, Class, enums, annotations,和前面类型的数组。方法可以有默认值。下面是一个注解类型的定义:
- public @interface RequestForEnhancement {
- int id();
- String synopsis();
- String engineer() default "[unassigned]";
- String date() default "[unimplemented]";
- }
复制代码
- 一旦一个注解类型被定义了,你可以使用它来进行注解声明。一个注解就是一个特殊的修饰符,可以被使用在任何其他修饰符可以出现的地方(比如 public, static, final)。按照惯例注解会放在其他修饰符之前。注解由一个@标志后跟注解类型组成,在后面是一对圆括弧里面是键值对的列表。值必须是编译时的常量。下面是一个方法声明用上面的注释定义进行注释:
- @RequestForEnhancement(
- id = 2868724,
- synopsis = "Enable time-travel",
- engineer = "Mr. Peabody",
- date = "4/1/3007"
- )
- public static void travelThroughTime(Date destination) { ... }
复制代码
- 一个注解类型如果没有元素就是一个”标记注解类型”,例如:
- public @interface Preliminary { }
复制代码
- @Preliminary public class TimeTravel { ... }
复制代码
- 如果注解只有一个元素那么元素不能有默认值,如下所示:
- public @interface Copyright {
- String value();
- }
复制代码
- 在单一元素的注解使用时可以省略元素名和等号直接写值,如下所示:
- @Copyright("2002 Yoyodyne Propulsion Systems")
- public class OscillationOverthruster { ... }
复制代码
- 为了把上面所说的联系到一起。我们将建立一个简单的基于注解的测试程序。首先我们需要一个标记注解类型来表明一个方法是测试方法应该用测试工具运行:
- import java.lang.annotation.*;
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.METHOD)
- public @interface Test { }
复制代码
- 注意注解类型的定义是自我注释的。这样的注解就是元注解。第一个@Retention(RetentionPolicy.RUNTIME)表明这个注解会被保留到虚拟机运行时所以他们可以在运行时通过反射读取。第二个@Target(ElementType.METHOD)表明这个注解类型只能被使用到方法定义。
- 下面是一个实例程序,它的一些方法使用上面的注解进行注释:
- public class Foo {
- @Test public static void m1() { }
- public static void m2() { }
- @Test public static void m3() {
- throw new RuntimeException("Boom");
- }
- public static void m4() { }
- @Test public static void m5() { }
- public static void m6() { }
- @Test public static void m7() {
- throw new RuntimeException("Crash");
- }
- public static void m8() { }
- }
复制代码
- import java.lang.reflect.*;
-
- public class RunTests {
- public static void main(String[] args) throws Exception {
- int passed = 0, failed = 0;
- for (Method m : Class.forName(args[0]).getMethods()) {
- if (m.isAnnotationPresent(Test.class)) {
- try {
- m.invoke(null);
- passed++;
- } catch (Throwable ex) {
- System.out.printf("Test %s failed: %s %n", m, ex.getCause());
- failed++;
- }
- }
- }
- System.out.printf("Passed: %d, Failed %d%n", passed, failed);
- }
- }
复制代码
- 这个工具使用一个命令行参数作为类名,迭代这个类的所有方法,并调用每个被Test注解注释的方法(上面定义)。反射的查询是否一个方法有注解定义存在。如果一个测试方法调用抛出了异常,这个测试方法就失败了,打印失败报告。最终打印成功的和失败的方法数。下面是在Foo程序上运行测试工具的输出:
- $ java RunTests Foo
- Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
- Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
- Passed: 2, Failed 2
复制代码
- 很明显这个测试工具是一个玩具程序,它演示了注解的力量并且可以很容易的被扩展以克服它的局限性。
|
|