第35条:注解优先于命名模式
Java 1.5之前,一般使用命名模式表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,JUnit测试框架原本要求用户一定要用test作为测试方法名称的开头。
命名模式的缺点:
1.文字拼写错误导致失败,测试方法没有执行,也没有报错。
2.无法确保它们只用于相应的程序元素上,如希望一个类的所有方法被测试,把类命名为test开头,但JUnit不支持类级的测试,只在test开头的方法中生效。
3.没有提供将参数值与程序元素关联起来的好方法。
注解能解决命名模式存在的问题,下面定义一个注解类型指定简单的测试,它们自动运行,并在抛出异常时失败(注意,下面的Test注解是自定义的,不是JUnit的实现。
/**
* 35:注解优先于命名模式
*/
public class AnnotationTest {
public static void main(String[] args) throws ClassNotFoundException {
int tests = 0;
int passed = 0;
Class testClass = Class.forName("com.learn.chaptertest.service4.Sample");
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 e) {
System.out.println("INVALID @Test: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
System.out.println("--------------------------------");
tests = 0;
passed = 0;
Class testClass2 = Class.forName("com.learn.chaptertest.service4.Sample2");
for(Method m : testClass2.getDeclaredMethods()) {
if(m.isAnnotationPresent(ExceptionTest.class)) {//判断是否有注解
tests++;
try {
m.invoke(null);
// passed++;
System.out.printf("Test %s failed : no exception%n",m);
} catch(InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
Class<? extends Exception> exType = m.getAnnotation(ExceptionTest.class).value();
if (exType.isInstance(exc) ) {
passed ++;
}else {
System.out.println("Test:"+ m + " failed: expected:" + exType.getName() + "got" + exc);
}
} catch(Exception e) {
System.out.println("INVALID @Test: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);
}
}
//简单的测试接口
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test {
}
//调用测试接口的类
class Sample {
@Test public static void m1() {
}
public static void m2() {
}
@Test public static void m3() {
throw new RuntimeException("Boom");
}
public static void m4() {
}
@Test public void m5() {
}
public static void m6() {
}
@Test public static void m7() {
throw new RuntimeException("Crash");
}
public static void m8() {
}
}
//针对只有在抛出特殊异常才成功的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ExceptionTest {
Class<? extends Exception> value();
}
//
class Sample2 {
@ExceptionTest(ArithmeticException.class)
static void m1 (){
int i = 0 ;
i = i/i;
}
@ExceptionTest(ArithmeticException.class)
static void m2 (){
int [] a = new int[0];
int i = a[1];
}
@ExceptionTest(ArithmeticException.class)
static void m3 (){
}
}
既然有了注解 , 就不必再用命名模式了。
除了特定的程序员之外 , 大多数程序员都不必定义注解类型。 但是所有的程序员都应该使用Java平台所提供的预定义的注解类型。 还要考虑 IDE 或者静态分析工具所提供的任何注解。 这种注解可以提升由这些工具所提供的诊断信息的质量。 但是要注意这些注解还没有标准化 , 因此如果变换工具或者形成标准 , 就有很多工作要做了。