第35條:注解優(yōu)先于命名模式

命名模式的缺點(diǎn):
1.文字拼寫錯(cuò)誤導(dǎo)致失敗,測(cè)試方法沒有執(zhí)行,也沒有報(bào)錯(cuò) (JUNIT測(cè)試框架測(cè)試的方法要用test開頭)
2.無(wú)法確保它們只用于相應(yīng)的程序元素上,如希望一個(gè)類的所有方法被測(cè)試,把類命名為test開頭,但JUnit不支持類級(jí)的測(cè)試,只在test開頭的方法中生效
3.沒有提供將參數(shù)值與程序元素關(guān)聯(lián)起來的好方法。想要支持一種測(cè)試類別,它只在拋出特殊異常時(shí)才會(huì)成功。異常類型本質(zhì)是測(cè)試的一個(gè)參數(shù),如果命名類不存在,或者不是一個(gè)異常,你只有通過運(yùn)行后才能發(fā)現(xiàn)。
注解能解決命名模式存在的問題,下面定義一個(gè)注解類型指定簡(jiǎn)單的測(cè)試,它們自動(dòng)運(yùn)行,并在拋出異常時(shí)失敗(注意,下面的Test注解是自定義的,不是JUnit的實(shí)現(xiàn))

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Test {
   
}

像test使用了Retention和Target 這兩種注解,這種注解被稱為元注解
@Retention(RetentionPolicy.RUNTIME)表明Test注解在運(yùn)行時(shí)保留,如果沒有保留,測(cè)試工具無(wú)法知道Test注解
@Target(ElementType.METHOD)表明只有在方法聲明中Test注解才是合法的,它不能運(yùn)用到類聲明,域聲明或者其他程序元素上。
use only on parameterless static method (只用于無(wú)參的靜態(tài)方法),但是編譯器并不能做到對(duì)參數(shù)進(jìn)行限制,如果將Test注解放在實(shí)例方法中,或者放在帶有一個(gè)或者多個(gè)的方法中,測(cè)試程序還是不會(huì)編譯錯(cuò)誤,只能讓測(cè)試工具運(yùn)行的時(shí)候進(jìn)行處理

下面的Sample類使用Test注解,如果拼錯(cuò)Test或者將Test注解應(yīng)用到除方法外的其他地方,
編譯不會(huì)通過

public 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() {
 thrownew  RuntimeException("Crash");
}
public  static  void  m8() {
}
}

在Sample 中有八個(gè)方法(其中m5不是靜態(tài)方法),四個(gè)被注解為測(cè)試的方法中,有兩個(gè)拋出異常:m3和m7,另外兩個(gè)沒有:m1和m5,被注解方法m5是一個(gè)實(shí)例方法,因此不屬于注解的有效使用。沒有進(jìn)行標(biāo)記的方法則會(huì)被測(cè)試工具忽略
test注解對(duì)Sample類的語(yǔ)義沒有直接影響,只負(fù)責(zé)提供信息供相關(guān)程序使用。也就是注解不會(huì)改變被注解代碼的語(yǔ)義,但是它可以通過工具進(jìn)行特殊的處理。比如咱們用注解對(duì)方法進(jìn)行簡(jiǎn)單的測(cè)試。
測(cè)試Sample的測(cè)試運(yùn)行類:

public class RunTests {

    public static void main(String[] args) throws Exception {

        int tests = 0;

        int passed = 0;

        Class testClass = Class.forName("service.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);

    }

}

測(cè)試運(yùn)行工具在命令行上使用完全匹配的類名,并通過調(diào)用Method.invoke反射式的運(yùn)行類中所有標(biāo)注了test的方法,isAnnotationPresent 方法告知工具要運(yùn)行哪些方法。如果測(cè)試方法拋出異常反射機(jī)制就會(huì)將錯(cuò)誤信息封裝到InvocationTargetException中,該工具捕捉到了這個(gè)異常,并且打印失敗報(bào)告,包含測(cè)試方法拋出的原始異常,這些信息是通過getCasuse方法從InvocationTargetException中提取出來的
如果嘗試通過反射調(diào)用測(cè)試方法時(shí)拋出InvocationTargetException之外的任何異常,表面編譯的時(shí)候沒有捕捉到Test注解的無(wú)效用法,這種用法包括實(shí)例方法的注解,或者帶一個(gè)或者多個(gè)參數(shù)的方法的注解,并且打印相應(yīng)的錯(cuò)誤信息
運(yùn)行結(jié)果:

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

針對(duì)只有在拋出特殊異常才成功的注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

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

    @ExceptionTest(ArithmeticException.class)
    public static  void  m1() {
    
    }

    public static void m2() {
    
    }
    
    @ExceptionTest(ArithmeticException.class)
    public static void  m3() {
    
        throw new   RuntimeException("Boom");
    
    }

    public static void   m4() {
    
    }
    @ExceptionTest(ArithmeticException.class)
    public  void  m5() {
    
    }
    
    public  static  void  m6() {
    
    }
    @ExceptionTest(ArithmeticException.class)
    public  static  void  m7() {
    
        throw new  RuntimeException("Crash");
    
    }
    
    public  static  void  m8() {

}

這段代碼類似于用來處理Test注解的代碼,但有一處不同:這段代碼提取了注解參數(shù)的值,并用它檢驗(yàn)該測(cè)試拋出的異常是否是正確的類型。沒有顯示的轉(zhuǎn)換,因此沒有出現(xiàn)類型轉(zhuǎn)換異常的危險(xiǎn),編譯過的測(cè)試程序確保它的注解參數(shù)表示的是有效的異常類型,需要提醒一點(diǎn):有可能注解參數(shù)參數(shù)在編譯時(shí)是有效的,但是表示特定異常類型的類文件在運(yùn)行時(shí)卻不再存在,在這種希望很少出現(xiàn)的情況下,測(cè)試運(yùn)行類會(huì)拋出TypeNotPresentException異常。
將上面的異常測(cè)試示例再深入一點(diǎn),測(cè)試可以拋出任何一種指定異常時(shí)都得到通過。我們將exceptionTest注解的參數(shù)類型改為Class對(duì)象的一個(gè)數(shù)組:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

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

注解中數(shù)組參數(shù)的語(yǔ)法十分靈活。它是進(jìn)行過優(yōu)化的單元數(shù)組。使用了ExceptionTest新版的數(shù)組參數(shù)之后,之前的所有的ExceptionTest注解依然有效,并產(chǎn)生單元素包圍起來,為了指定多元素的數(shù)組,需要用({})將元素保衛(wèi)起來,并且用{,}隔開

public class Sample2 {

    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public static  void  m1() {
    
    }

    public static void m2() {
    
    }
    
    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public static void  m3() {
    
        throw new   RuntimeException("Boom");
    
    }

    public static void   m4() {
    
    }
    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public  void  m5() {
    
    }
    
    public  static  void  m6() {
    
    }
    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public  static  void  m7() {
    
        throw new  RuntimeException("Crash");
    
    }
    
    public  static  void  m8() {

}

}
public class RunTests2 {

    public static void main(String[] args) throws Exception {

        int tests = 0;

        int passed = 0;

        Class testClass = Class.forName("service.Sample2");

        for (Method m : testClass.getDeclaredMethods()) {

            if (m.isAnnotationPresent(ExceptionTest1.class)) {
                tests++;

                try { //反射式的運(yùn)行所有標(biāo)注了Test的方法

                    m.invoke(null);

                } catch (InvocationTargetException e) {
                    //InvocationTargetException異常由Method.invoke(obj, args...)方法拋出。當(dāng)被調(diào)用的方法的內(nèi)部拋出了異常而沒有被捕獲時(shí),將由此異常接收。
                    Throwable exc = e.getCause();
                    Class[] excTypes = m.getAnnotation(ExceptionTest1.class).value();
                    int oldPassed = passed;
                    for (Class excType : excTypes) {
                        if (excType.isInstance(exc)) {
                            passed++;
                            break;
                        }
                    }
                    if (passed == oldPassed) {
                        System.out.printf("測(cè)試%s失敗:%s %n", m, exc);
                    }

                } catch (Exception e) {

                    System.out.println("Invalid @Test: " + m);

                }

            }

        }

        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);

    }

}

以上的例子不揭露了注解的冰山一角 , 但它鮮明了表達(dá)了一個(gè)觀點(diǎn) , 既然有了注解 , 就不必再用命名模式了
總結(jié):除了特定的程序員之外 , 大多數(shù)程序員都不必定義注解類型 . 但是所有的程序員都應(yīng)該使用Java平臺(tái)所提供的預(yù)定義的注解類型 . 還要考慮 IDE(集成開發(fā)環(huán)境(IDE,Integrated Development Environment )是用于提供程序開發(fā)環(huán)境的應(yīng)用程序,一般包括代碼編輯器、編譯器、調(diào)試器和圖形用戶界面等工具) 或者靜態(tài)分析工具所提供的任何注解 . 這種注解可以提升由這些工具所提供的診斷信息的質(zhì)量 . 但是要注意這些注解還沒有標(biāo)準(zhǔn)化 , 因此如果變換工具或者形成標(biāo)準(zhǔn) , 就需要做更多地工作 .

。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,253評(píng)論 6 342
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,716評(píng)論 25 709
  • 凱文凱利的《必然》,在一年多前讀了,當(dāng)時(shí)很是震撼。最近“得到”分11期進(jìn)行了解讀。 重新來過后,里面的一些現(xiàn)象在現(xiàn)...
    不騖于虛聲閱讀 162評(píng)論 0 0

友情鏈接更多精彩內(nèi)容