Java 注解原理詳細介紹

什么是注解

Java 注解(Annotation)又稱 Java 標注或元數(shù)據(jù),是 JDK5.0 引入的新特性钉疫,用于對代碼進行說明寞钥,可以對包、類陌选、接口理郑、字段、方法參數(shù)咨油、局部變量等進行注解您炉。例如我們常見的@Override、@Deprecated役电、@Test等赚爵。

簡單來說,可以把注解理解成代碼中的特殊標記,這些標記可以在編譯冀膝、類加載唁奢、運行時被讀取,并執(zhí)行相應的處理窝剖。通過注解麻掸,開發(fā)人員可以在不改變原有代碼情況下,在源代碼中嵌入補充信息赐纱。


annotation.png

注解的作用

  1. 生成文檔:通過代碼里標識的元數(shù)據(jù)生成javadoc文檔脊奋;
  2. 編譯檢查:通過代碼里標識的元數(shù)據(jù),讓編譯器在編譯期間進行檢查疙描;
  3. 編譯時動態(tài)處理:例如編譯時通過代碼里標識的元數(shù)據(jù)诚隙,動態(tài)生成代碼;
  4. 運行時動態(tài)處理:運行時通過代碼里標識的元數(shù)據(jù)動態(tài)處理起胰,例如使用反射注入實例

注解語法和使用

通常久又,注解可分為以下三類:
1. 元注解
元注解是用于定義注解的注解,java.lang.annotation提供了四種元注解效五,專門注解其他的注解:

  • @Documented: 標明是否生成javadoc文檔
  • @Retention: 標明注解被保留的階段地消,即什么時候使用該注解
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息僅存在于編譯器處理期間,編譯器處理完之后就沒有該Annotation信息了  */
    CLASS,             /* 編譯器將Annotation存儲于類對應的.class文件中火俄,默認行為  */
    RUNTIME            /* 編譯器將Annotation存儲于class文件中犯建,并且可由JVM讀入 */
}

例如 @Override, 當它修飾一個方法的時候瓜客,表明該方法是重寫父類的方法适瓦,編譯期間會進行語法檢查,但編譯器處理完后谱仪,@Override就沒有任何作用玻熙。

  • @Target: 標明注解使用的范圍,即該注解用于什么地方
public enum ElementType {
    TYPE,               /* 描述類疯攒、接口(包括注釋類型)或枚舉  */
    FIELD,              /* 描述成員變量嗦随、對象、屬性  */
    METHOD,             /* 用來描述方法 */
    PARAMETER,          /* 參數(shù)聲明  */
    CONSTRUCTOR,        /* 構造方法聲明  */
    LOCAL_VARIABLE,     /* 描述局部變量  */
    ANNOTATION_TYPE,    /* 注釋類型聲明  */
    PACKAGE             /* 包聲明  */
}
  • @Inherited: 標明注解可繼承敬尺,是否允許子類繼承該注解

2. Java常用注解
@Override: 標明重寫某個方法
@Deprecated: 標明某個類或方法過時
@SuppressWarnings: 標明要忽略的警告枚尼,
當代碼中使用這些注解后,編譯器就會進行檢查砂吞。

例如署恍,這兩段代碼是@Override和@SuppressWarnings的Java源文件:

[Override.java]
@Target(ElementType.METHOD)                       /* 表明@Override用于描述方法  */
@Retention(RetentionPolicy.SOURCE)                /* 表明@Override僅在于編譯器處理期間存在  */
public @interface Override {
}

[SuppressWarnings.java]
 /*   表明@SuppressWarnings可以描述方法、字段蜻直、參數(shù)盯质、構造方法類等  */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) 
@Retention(RetentionPolicy.SOURCE)             /*   表明@SuppressWarnings僅在于編譯器處理期間存在     */
public @interface SuppressWarnings {
    String[] value();
}

@interface: 表示實現(xiàn)了 java.lang.annotation.Annotation 接口袁串,即該注解就是一個Annotation。
定義 Annotation 時呼巷,@interface 是必須的囱修。
注意:它和通常的 implemented 實現(xiàn)接口的方法不同,Annotation 接口的實現(xiàn)細節(jié)都由編譯器完成王悍。
通過 @interface 定義注解后破镰,該注解不能繼承其他的注解或接口。
@Override使用很常見配名,來看一個使用@SuppressWarnings的例子:

@SuppressWarnings("deprecation")
private void push() {
    ......
}

如果push() 方法是過期的方法啤咽,編譯時就會產(chǎn)生警告晋辆。而使用了 @SuppressWarnings(value={"deprecation"})后渠脉,編譯器會對"調(diào)用 push() 產(chǎn)生的警告"保持沉默。

3. 自定義注解
可以根據(jù)自己的需求定義注解瓶佳,如寫UT/IT case 時使用到的@Test芋膘,Google 開源依賴注入框架Dagger2中的@Inject、@Module等

@Test注解后霸饲,在運行該方法時为朋,測試框架會自動識別該方法并單獨調(diào)

注解處理器

如果沒有注解處理器,那注解跟注釋其實沒有什么區(qū)別厚脉,下面是Thinking Java的一個Demo习寸,使用注解創(chuàng)建數(shù)據(jù)表:

1. 定義用來創(chuàng)建表名的注解@DBTable:

//DBTable.java
@Target(ElementType.TYPE) //Applies to classes only
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    public String name() default "";
}

2. 定義用來約束的注解@Constraints:

//Constraints.java
@Target(ElementType.FIELD) 
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primarykey() default false;
    boolean allowNull() default true;
    boolean unique() default false;
}

3. 定義聲明String類型的注解@SQLString:

//SQLString.java
@Target(ElementType.FIELD)
public @interface SQLString {
    int value() default 0;
    String name() default "";
    Constraints constraints() default @Constraints;
}

4. 定義聲明Integer類型的注解@SQLInteger:

//SQLInteger.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraints() default @Constraints;
}

注意在SQLString.java和SQLInteger.java中constraints()元素的默認值就是@Constraints注解設定的默認值。如果現(xiàn)在要令嵌入的@Constraints注解中的unique()元素為true傻工, 并以此作為constraints元素的默認值霞溪,則需要如下定義該元素

//Uniqueness.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uniqueness {
    Constraints constraints() default @Constraints(unique = true)
}

5. 使用以上這些注解,定義一個數(shù)據(jù)庫表:

//Member.java
@DBTable(name = "MEMBER")
public class Member {
    @SQLString(30) String firstName;
    @SQLString(50) String lastName;
    @SQLInteger Integer age;
    @SQLString(value = 30, constraints = @Constraints(primarykey = true))
    String handle;
    static int memberCount;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public Integer getAge() {
        return age;
    }

    public String getHandle() {
        return handle;
    }

    public static int getMemberCount() {
        return memberCount;
    }
}

6. 實現(xiàn)注解處理器:

//TableCreator.java
{Args:com.sdkd.database.Member}
public class TableCreator {
    private static String getConstraints(Constraints con) {
        String constraints = "";
        if(!con.allowNull()) constraints += "NOT NULL";
        if(con.primarykey()) constraints += "PRIMARY KEY";
        if(con.unique()) constraints += " UNIQUE";
        return constraints;
    }
    public static void main(String[] args)throws Exception {
        if(args.length < 1) {
            System.out.println("arguments: annotated classes");
            System.exit(0);
        }
        for(String className : args) {
            Class<?> cl = Class.forName(className);
            DBTable dbTable = cl.getAnnotation(DBTable.class);
            if(dbTable == null) {
                System.out.println("No DBTable annotations in class" + className);
                continue;
            }
            String tableName = dbTable.name();
            //If the name is empty, use the Class name
            if(tableName.length() < 1) {
                tableName = cl.getName().toUpperCase();
            }
            List<String> columnDefs = new ArrayList<String>();
            for(Field field : cl.getDeclaredFields()) {
                String columnName = null;
                Annotation[] anns = field.getDeclaredAnnotations();
                if (anns.length < 1) continue;
                if (anns[0] instanceof SQLInteger) {
                    SQLInteger sInt = (SQLInteger) anns[0];
                    //Use field name if name not specified
                    if (sInt.name().length() < 1) {
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sInt.name();
                    }
                    columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
                }
                if (anns[0] instanceof SQLString) {
                    SQLString sString = (SQLString) anns[0];
                    if (sString.name().length() < 1) {
                        columnName = field.getName().toUpperCase();
                    } else {
                        columnName = sString.name();
                    }
                    columnDefs.add(columnName + " VARCHAR(" +
                            sString.value() + ")" +
                            getConstraints(sString.constraints()));
                }
            }
            StringBuilder createCommand = new StringBuilder(
                    "CREATE TABLE " + tableName + "("
            );
            for(String columnDef : columnDefs) {
                createCommand.append("\n    " + columnDef + ",");
                String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");";
                System.out.println("Table Creation SQL for " +
                        className + " is :\n" + tableCreate);
            }
        }
    }
}

7. main()方法測試:

public class Test {
    public static void main(String[] args) throws Exception {
        String[] arg = {"com.sdkd.database.Member"};
        new TableCreator().main(arg);
    }
}

7. 輸出:

Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50));
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50),
    AGE INT);
Table Creation SQL for com.sdkd.database.Member is :
CREATE TABLE MEMBER(
    FIRSTNAME VARCHAR(30),
    LASTNAME VARCHAR(50),
    AGE INT,
    HANDLE VARCHAR(30)PRIMARY KEY);
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末中捆,一起剝皮案震驚了整個濱河市鸯匹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泄伪,老刑警劉巖殴蓬,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蟋滴,居然都是意外死亡染厅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門津函,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肖粮,“玉大人,你說我怎么就攤上這事球散∧蜃” “怎么了散庶?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凌净。 經(jīng)常有香客問我悲龟,道長,這世上最難降的妖魔是什么冰寻? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任须教,我火速辦了婚禮,結果婚禮上斩芭,老公的妹妹穿的比我還像新娘轻腺。我一直安慰自己,他們只是感情好划乖,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布贬养。 她就那樣靜靜地躺著,像睡著了一般琴庵。 火紅的嫁衣襯著肌膚如雪误算。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼递鹉。 笑死幢哨,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼知纷!你這毒婦竟也來了?” 一聲冷哼從身側響起导披,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤屈扎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后撩匕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹰晨,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年止毕,在試婚紗的時候發(fā)現(xiàn)自己被綠了模蜡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡扁凛,死狀恐怖忍疾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谨朝,我是刑警寧澤卤妒,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布甥绿,位于F島的核電站,受9級特大地震影響则披,放射性物質(zhì)發(fā)生泄漏共缕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一士复、第九天 我趴在偏房一處隱蔽的房頂上張望图谷。 院中可真熱鬧,春花似錦阱洪、人聲如沸便贵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽承璃。三九已至,卻和暖如春俏竞,著一層夾襖步出監(jiān)牢的瞬間绸硕,已是汗流浹背堂竟。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工魂毁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人出嘹。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓席楚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親税稼。 傳聞我的和親對象是個殘疾皇子烦秩,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355