【轉(zhuǎn)】公共技術(shù)點之 Java 注解 Annotation

不少開源庫都用到了注解的方式來簡化代碼提高開發(fā)效率屑墨。本文簡單介紹下 Annotation 示例纷铣、概念及作用、分類以躯、自定義忧设、解析颠通,并對幾個 Android 開源庫 Annotation 原理進(jìn)行簡析

1. Annotation 示例

Override Annotation
@Override
public void onCreate(Bundle savedInstanceState);

Retrofit Annotation
@GET("/users/{username}")
User getUser(@Path("username") String username);

Butter Knife Annotation
@InjectView(R.id.user) 
EditText username;

ActiveAndroid Annotation
@Column(name = “Name") 
public String name;

Retrofit 為符合 RESTful 規(guī)范的網(wǎng)絡(luò)請求框架谨垃,Butter Knife 為 View 及事件等依賴注入框架刘陶,Active Android 為 ORM 框架匙隔。更多見:Android 開源項目匯總

2. Annotation 概念及作用

2.1 概念

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

能夠添加到 Java 源代碼的語法元數(shù)據(jù)纷责。類纳决、方法乡小、變量满钟、參數(shù)、包都可以被注解夭织,可用來將信息元數(shù)據(jù)與程序元素進(jìn)行關(guān)聯(lián)尊惰。Annotation 中文常譯為“注解”。

2.2 作用

a. 標(biāo)記弄屡,用于告訴編譯器一些信息
b. 編譯時動態(tài)處理膀捷,如動態(tài)生成代碼
c. 運行時動態(tài)處理,如得到注解信息
這里的三個作用實際對應(yīng)著后面自定義 Annotation 時說的 @Retention 三種值分別表示的 Annotation

public class Person { 
    private int id; 
    private String name; 
    public Person(int id, String name) { 
        this.id = id; 
        this.name = name; 
    } 
    public boolean equals(Person person) { 
        return person.id == id; 
    } 
    public int hashCode() { 
        return id; 
    } 

    public static void main(String[] args) { 
        Set<Person> set = new HashSet<Person>(); 
        for (int i = 0; i < 10; i++) { 
            set.add(new Person(i, "Jim")); 
        } 
        System.out.println(set.size()); 
    }
}

上面的運行結(jié)果是多少秀仲? 10

3. Annotation 分類

3.1 標(biāo)準(zhǔn) Annotation神僵,Override, Deprecated, SuppressWarnings

標(biāo)準(zhǔn) Annotation 是指 Java 自帶的幾個 Annotation挑豌,上面三個分別表示重寫函數(shù)墩崩,不鼓勵使用(有更好方式、使用有風(fēng)險或已不在維護(hù))铝阐,忽略某項 Warning

3.2 元 Annotation徘键,@Retention, @Target, @Inherited, @Documented

元 Annotation 是指用來定義 Annotation 的 Annotation遍蟋,在后面 Annotation 自定義部分會詳細(xì)介紹含義

3.3 自定義 Annotation

自定義 Annotation 表示自己根據(jù)需要定義的 Annotation虚青,定義時需要用到上面的元 Annotation這里是一種分類而已,也可以根據(jù)作用域分為源碼時纵穿、編譯時奢人、運行時 Annotation,后面在自定義 Annotation 時會具體介紹

4. Annotation 自定義

4.1 調(diào)用

public class App { 
    @MethodInfo( 
        author = “trinea.cn+android@gmail.com”, 
        date = "2014/02/14", 
        version = 2) 
    public String getAppName() { 
        return "trinea"; 
    }
}

這里是調(diào)用自定義 Annotation——MethodInfo 的示例句惯。MethodInfo Annotation 作用為給方法添加相關(guān)信息,包括 author脯燃、date辕棚、version棕所。

4.2 定義

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo { 
    String author() default "trinea@gmail.com"; 
    String date(); 
    int version() default 1;
}

這里是 MethodInfo 的實現(xiàn)部分
(1). 通過 @interface 定義伐债,注解名即為自定義注解名
(2).注解配置參數(shù)名為注解類的方法名蹋绽,且:
a. 所有方法沒有方法體昧互,沒有參數(shù)沒有修飾符,實際只允許 public & abstract 修飾符叽掘,默認(rèn)為 public玖雁,不允許拋異常
b. 方法返回值只能是基本類型赫冬,String, Class, annotation, enumeration 或者是他們的一維數(shù)組
c. 若只有一個默認(rèn)屬性,可直接用 value() 函數(shù)膛薛。一個屬性都沒有表示該 Annotation 為 Mark Annotation
(3).可以加 default 表示默認(rèn)值

4.3 元 Annotation

@Documented 是否會保存到 Javadoc 文檔中
@Retention 保留時間补鼻,可選值 SOURCE(源碼時)辽幌,CLASS(編譯時)椿访,RUNTIME(運行時)成玫,默認(rèn)為 CLASS拳喻,SOURCE 大都為 Mark Annotation冗澈,這類 Annotation 大都用來校驗亚亲,比如 Override, SuppressWarnings
@Target 可以用來修飾哪些程序元素腐缤,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未標(biāo)注則表示可修飾所有
@Inherited 是否可以被繼承惜索,默認(rèn)為 false

5. Annotation 解析

5.1 運行時 Annotation 解析

(1) 運行時 Annotation 指 @Retention 為 RUNTIME 的 Annotation剃浇,可手動調(diào)用下面常用 API 解析

method.getAnnotation(AnnotationName.class);
method.getAnnotations();
method.isAnnotationPresent(AnnotationName.class);

其他 @Target 如 Field虎囚,Class 方法類似
getAnnotation(AnnotationName.class) 表示得到該 Target 某個 Annotation 的信息,因為一個 Target 可以被多個 Annotation 修飾.
getAnnotations() 則表示得到該 Target 所有 Annotation.
isAnnotationPresent(AnnotationName.class) 表示該 Target 是否被某個 Annotation 修飾

(2) 解析示例如下:

public static void main(String[] args) { 
    try { 
        Class cls = Class.forName("cn.trinea.java.test.annotation.App"); 
        for (Method method : cls.getMethods()) { 
            MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); 
            if (methodInfo != null) { 
                System.out.println("method name:" + method.getName()); 
                System.out.println("method author:" + methodInfo.author()); 
                System.out.println("method version:" + methodInfo.version()); 
                System.out.println("method date:" + methodInfo.date()); 
            } 
        } 
    } catch (ClassNotFoundException e) { 
        e.printStackTrace(); 
    }
}

以之前自定義的 MethodInfo 為例吉拳,利用 Target(這里是 Method)getAnnotation 函數(shù)得到 Annotation 信息留攒,然后就可以調(diào)用 Annotation 的方法得到響應(yīng)屬性值

5.2 編譯時 Annotation 解析

(1)編譯時 Annotation 指 @Retention 為 CLASS 的 Annotation炼邀,甴編譯器自動解析。需要做的
a. 自定義類繼承自 AbstractProcessor
b. 重寫其中的 process 函數(shù)拭宁。這塊很多同學(xué)不理解杰标,實際是編譯器在編譯時自動查找所有繼承自 AbstractProcessor 的類腔剂,然后調(diào)用他們的 process 方法去處理
(2) 假設(shè) MethodInfo 的 @Retention 為 CLASS驼仪,解析示例如下:

@SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor { 
    @Override 
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { 
        HashMap<String, String> map = new HashMap<String, String>(); 
        for (TypeElement te : annotations) { 
            for (Element element : env.getElementsAnnotatedWith(te)) { 
                MethodInfo methodInfo = element.getAnnotation(MethodInfo.class); 
                map.put(element.getEnclosingElement().toString(), methodInfo.author()); 
            } 
        } 
        return false; 
    }
}

SupportedAnnotationTypes 表示這個 Processor 要處理的 Annotation 名字。process 函數(shù)中參數(shù) annotations 表示待處理的 Annotations湾碎,參數(shù) env 表示當(dāng)前或是之前的運行環(huán)境process 函數(shù)返回值表示這組 annotations 是否被這個 Processor 接受介褥,如果接受后續(xù)子的 rocessor 不會再對這個 Annotations 進(jìn)行處理

6. 幾個 Android 開源庫 Annotation 原理簡析

6.1 Annotation — Retrofit

(1) 調(diào)用

@GET("/users/{username}")
User getUser(@Path("username") String username);

(2) 定義

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET { 
    String value();
}

從定義可看出 Retrofit 的 Get Annotation 是運行時 Annotation,并且只能用于修飾 Method
(3) 原理

private void parseMethodAnnotations() { 
    for (Annotation methodAnnotation : method.getAnnotations()) { 
        Class<? extends Annotation> annotationType = methodAnnotation.annotationType();       
        RestMethod methodInfo = null; 
        for (Annotation innerAnnotation : annotationType.getAnnotations()) { 
            if (RestMethod.class == innerAnnotation.annotationType()) { 
                methodInfo = (RestMethod) innerAnnotation; 
                break; 
            } 
        } 
        …… 
    }
}

RestMethodInfo.java 的 parseMethodAnnotations 方法如上雹顺,會檢查每個方法的每個 Annotation嬉愧, 看是否被 RestMethod 這個 Annotation 修飾的 Annotation 修飾没酣,這個有點繞卵迂,就是是否被 GET、DELETE偿衰、POST改览、PUT、HEAD视事、PATCH 這些 Annotation 修飾庆揩,然后得到 Annotation 信息订晌,在對接口進(jìn)行動態(tài)代理時會掉用到這些 Annotation 信息從而完成調(diào)用。
Retrofit 原理涉及到動態(tài)代理砌庄,這里原理都只介紹 Annotation鹤耍,具體原理分析請見 Android 開源項目實現(xiàn)原理解析

6.2 Annotation — Butter Knife

(1) 調(diào)用

@InjectView(R.id.user) 
EditText username;

(2) 定義

@Retention(CLASS) 
@Target(FIELD)
public @interface InjectView { 
    int value();
}

可看出 Butter Knife 的 InjectView Annotation 是編譯時 Annotation稿黄,并且只能用于修飾屬性
(3) 原理

@Override 
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { 
    Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env); 
    for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) { 
        TypeElement typeElement = entry.getKey(); 
        ViewInjector viewInjector = entry.getValue(); 
        try { 
            JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement); 
            Writer writer = jfo.openWriter(); 
            writer.write(viewInjector.brewJava()); 
            writer.flush(); 
            writer.close(); 
        } catch (IOException e) { 
            error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage()); 
        } 
    } 
    return true;
}

ButterKnifeProcessor.java 的 process 方法如上杆怕,編譯時壳贪,在此方法中過濾 InjectView 這個 Annotation 到 targetClassMap 后违施,會根據(jù) targetClassMap 中元素生成不同的 class 文件到最終的 APK 中,然后在運行時調(diào)用 ButterKnife.inject(x) 函數(shù)時會到之前編譯時生成的類中去找磕蒲。 這里原理都只介紹 Annotation,具體原理分析請見Android 開源項目實現(xiàn)原理解析

6.3 Annotation — ActiveAndroid

(1) 調(diào)用

@Column(name = “Name") 
public String name;

(2) 定義

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column { 
    ……
}

可看出 ActiveAndroid 的 Column Annotation 是運行時 Annotation兔院,并且只能用于修飾屬性坊萝。
(3) 原理

Field idField = getIdField(type);
mColumnNames.put(idField, mIdName);
List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));
Collections.reverse(fields);
for (Field field : fields) { 
    if (field.isAnnotationPresent(Column.class)) { 
        final Column columnAnnotation = field.getAnnotation(Column.class); 
        String columnName = columnAnnotation.name(); 
        if (TextUtils.isEmpty(columnName)) { 
            columnName = field.getName(); 
        } 
        mColumnNames.put(field, columnName); 
    }
}

TableInfo.java 的構(gòu)造函數(shù)如上,運行時许起,得到所有行信息并存儲起來用來構(gòu)件表信息。
這里原理都只介紹 Annotation扯键,具體原理分析請見 Android 開源項目實現(xiàn)原理解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荣刑,一起剝皮案震驚了整個濱河市厉亏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烈和,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窝趣,死亡現(xiàn)場離奇詭異训柴,居然都是意外死亡,警方通過查閱死者的電腦和手機洗鸵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門膘滨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稀拐,“玉大人德撬,你說我怎么就攤上這事∶” “怎么了踊东?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵闸翅,是天一觀的道長菊霜。 經(jīng)常有香客問我,道長记某,這世上最難降的妖魔是什么液南? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任勾徽,我火速辦了婚禮,結(jié)果婚禮上畅姊,老公的妹妹穿的比我還像新娘。我一直安慰自己朱嘴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布舌劳。 她就那樣靜靜地躺著甚淡,像睡著了一般捅厂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撵割,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天啡彬,我揣著相機與錄音故硅,去河邊找鬼庶灿。 笑死,一個胖子當(dāng)著我的面吹牛吃衅,可吹牛的內(nèi)容都是我干的往踢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼徘层,長吁一口氣:“原來是場噩夢啊……” “哼峻呕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趣效,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤山上,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后英支,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佩憾,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年楞黄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碎税。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萎庭,到底是詐尸還是另有隱情,我是刑警寧澤署海,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布趾代,位于F島的核電站禽捆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏芽隆。R本人自食惡果不足惜牙躺,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秋茫,春花似錦学辱、人聲如沸衙傀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擎析。三九已至,卻和暖如春现斋,著一層夾襖步出監(jiān)牢的瞬間瞬内,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留舟陆,地道東北人裆装。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親懒熙。 傳聞我的和親對象是個殘疾皇子工扎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容