【笔记39】注解优先于命名模式

Java1.5发行版本之前,一般使用命名模式(naming pattern)表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,JUnit测试框架原本要求他的用户一定要用test作为测试方法名称的开头【Beck04】。这种方法可行,但是有几个很严重的缺点。

  • 文字拼写错误导致失败,测试方法没有执行,也没有报错 (JUNIT测试框架测试的方法要用test开头)
  • 无法确保它们只用于相应的程序元素上,如希望一个类的所有方法被测试,把类命名为test开头,但JUnit不支持类级的测试,只在test开头的方法中生效
  • 没有提供将参数值与程序元素关联起来的好方法。想要支持一种测试类别,它只在抛出特殊异常时才会成功。异常类型本质是测试的一个参数,如果命名类不存在,或者不是一个异常,你只有通过运行后才能发现。

注解能解决命名模式存在的问题。假设想要定义一个注解类型来指定简单的测试,他们自动运行,并在抛出异常时失败。以下就是这样的一个注解类型,命名为Test:

/**
 * Indicates that the annotated method is a test method.
 * Use only on parameterless static methods.
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
 
}

Test注解类型的声明就是他自身通过Retention和Target注解进行了注解。注解类型声明中的这种注解被称作元注解(meta-annotation)。@Retention(RetentionPolicy.RUNTIME)元注解表明,Test注解应该在运行时保留。如果没有保留,测试工具就无法知道Test注解。@Target(ElementType.METHOD)元注解表明,Test注解只在方法声明中才是合法的:他不能运用到类声明、域声明或者其他程序元素上。

注意Test注解声明上方的注释:“Use only on parameterless static methods(只用于无参的静态方法)”。如果编译器能够强制这一限制最好,但是他做不到。编译器可以替你完成多少错误检查,这是有限制的,即使是利用注解。如果将Test注解放在实例方法的声明中,或者放在带有一个或者多个参数的方法中,测试程序还是可以编译,让测试工具在运行时来处理这个问题。

下面就是现实应用中的Test注解,称作标记注解(marker annotation),因为他没有参数,只是“标注”被注解的元素。如果程序员拼错了Test,或者将Test注解应用到程序元素而非方法声明,程序就无法编译:

public class Sample {
    @Test
    public static void ml() {} // Test shold pass
    public static void m2() {}
    @Test
    public static void m3() { // Test Should fail
        throw new RuntimeException("Boom");
    }
    public static void m4() {}
    @Test
    public void m5() {} // InVALID USE: nonstatic method
    public static void m6() {}
    @Test
    public static void m7() { // Test should fail
        throw new RuntimeException("Crash");
    }
    public static void m8() {}
}

 Sample类有8个静态方法,其中4个被注解为测试。这4个中有2个抛出了异常:m3和m7,另外两个则没有:m1和m5。但是其中一个没有抛出异常的被注解方法:m5,是一个实例方法,因此不属于注解的有效使用。。没有用Test注解进行标注的4个方法会被测试工具忽略。

Test注解对Sample类的语义没有直接的影响。他们只负责提供信息供相关的程序使用。更一般地讲,注解永远不会改变被注解代码的语义,但是使他可以通过工具进行特殊的处理,例如像这种简单的测试运行类:

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) {
            if(m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " failed:" + exc);
                } catch (Exception exc) {
                    System.out.println("INVALID @Test:" + m);
                }
            }
        }
        System.out.printf("Passed:%d,Failed:%d%n", passed, tests-passed);
    }
 
}

测试运行工具在命令行上使用完全匹配的类名,并通过调用Method.invoke反射式的运行类中所有标注了Test的方法。isAnnotationPresent方法告知该工具要运行哪些方法。如果测试方法抛出异常,反射机制就会将他封装在InvocationTargetException中。该工具捕捉到了这个异常,并打印失败报告,包含测试方法抛出的原始异常,这些信息是通过getCause方法从InvocationTargetException中提取出来的。

如果尝试通过反射调用测试方法时抛出InvocationTargetException之外的任何异常,表明编译时没有捕捉到Test注解的无效用法。这种用法包括实例方法的注解,或者带有一个或者多个参数的方法的注解,或者不可访问的方法的注解。测试运行类中的第二个catch块捕捉到了这些Test用法错误,并打印出相应的错误消息。

public   static   void   Sample.m3()
 failed: java.lang.RuntimeException: Boom
INVALID @Test:public void  Sample.m5()
public  static   void  Sample.m7()  
failed: java.lang.RuntimeException: Crash
Passed:1, Failed: 3

针对只有在抛出特殊异常才成功的测试提供支持,为此我们需要一个新的注解:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class <? extends Exception>value();
}

这个注解的参数类型是Class <? extends Exception>,即某个扩展Exception的类的Class对象,允许注解的用户指定任何异常类型。

将exceptionTest注解的参数类型改为Class对象的一个数组:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)
public @interface ExceptionTest1 {
    Class <? extends Exception> [] value();
}

除了“工具铁匠(toolsmiths——特定的程序员)”之外,大多数程序员都不必定义注解类型。但是所有的程序员都应该使用Java平台所提供的预定义的注解类型。还要考虑使用IDE或者静态分析工具所提供的任何注解。这种注解可以提升由这些工具所提供的诊断信息的质量。但是要注意这些注解还没有标准化,因此如果变换工具或者形成标准,就有很多工作要做了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值