最近我寫了一篇關於組件化的開源框架源碼分析的文章(傳送門在下面兒)。那麼如今組件化小有名氣的JIMU框架,也是我下一個要給你們分享的源碼分析文章。但由於其中涉及到了不少Java Annotation
相關的知識。因此不得不在這裏,先安利一下本篇,這也是本篇的由來。java
優秀框架源碼分析系列(一)讓解耦更輕鬆!多進程組件化框架-ModularizationArchitectureweb
「註解」,在Java世界裏隨處可見,但一般狀況下,多數人對其是視而不見的。但當咱們設計SDK,設計基礎庫的時候,運用註解,能夠起到簡化配置的做用。熟悉ButterKnife
的朋友都知道,它就是經過註解來在編譯期間,增長Java代碼來實現的。若是你還不知道它是如何實現的,那麼相信你食用完本篇和下一篇之後,就會明白這一切了。編程
學習新知識的時候,要掌握正確的進食方法,腦子裏必須先對知識結構有預期,學習完以後再回顧結構,根據結構記住知識。本篇將按照導圖的結構,來進行講解。數組
元註解,就是用來修飾註解的註解。bash
@Target
被用來指明此Annotation所修飾的對象範圍(即:被描述的註解能夠用在什麼地方)。Java中,註解(Annotation
)可被用於如下位置 :框架
註解的使用範圍,經過Target
的取值來指定。指定好之後,@Target
修飾的元素必定是與其取的value
相匹配的,不然編譯會報錯。ide
value
取值(ElementType
)常見的有:工具
ElementType | 含義 |
---|---|
CONSTRUCTOR | 用於描述構造器 |
FIELD | 用於描述域(包括enum常量) |
LOCAL_VARIABLE | 用於描述局部變量 |
METHOD | 用於描述方法 |
PACKAGE | 用於描述包 |
PARAMETER | 用於描述參數 |
TYPE | 用於描述類、接口(包括註解類型) 或enum聲明 |
注:PACKAGE,它並非使用在通常的類中,而是用在固定的文件package-info.java中。這裏須要強調命名必定是「package-info」源碼分析
這裏須要特殊說明的是,在之前的Java版本中,開發者只能將註解(Annotation)寫在聲明中。但從Java 8開始,註解能夠寫在使用類型的任何地方,例如聲明、泛型和強制類型轉換等語句:組件化
@Encrypted String str;
List<@NonNull String> strs;
test = (@Immutable Test) tmpTest;
複製代碼
針對這個拓展,JAVA8對原有的@Target的取值作了擴充,引入了新的類型(TYPE)
註解,即ElmentType
增長了:
ElementType | 含義 |
---|---|
TYPE_PARAMETER | 表示註解能夠用在類型變量的聲明語句中(如 class Test {...}) |
TYPE_USE | 表示註解能夠用在使用類型的任何語句中(如聲明語句、泛型和強轉) |
關於類型的解釋參考上文。
@Retention
,翻譯爲保留,指示了一個註解被保留的時間期限,一個被其修飾的註解會被保留到其value
指定三個階段的其中之一,若是註解類型聲明中不存在Retention
註解,則保留策略默認爲CLASS
:
RetentionPolicy | 含義 |
---|---|
RetentionPolicy.SOURCE | 只在源代碼級別保留,編譯時就會被忽略 |
RetentionPolicy.CLASS | 在編譯時被保留,在class文件中存在,但JVM將會忽略 |
RetentionPolicy.RUNTIME | 被JVM保留,因此他能在運行時被JVM或其餘使用反射機制的代碼所讀取和使用 |
被@Documented修飾的註解,用來表示這個被修飾註解應該被 javadoc工具記錄。默認狀況下,javadoc是不包括註解的。但若是聲明註解時指定了@Documented,則它會被javadoc之類的工具處理,因此註解類型信息也會被包括在生成的文檔中。
若是一個用來修飾class的註解,但願被這個class的sub-class繼承,則能夠對這個註解使用@Inherited修飾。 上面這句話強調了如下兩點:
自定義註解,經過@符號和interface關鍵字來定義。相似於class的寫法,例如:
package com.xm.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnno{
String name();
String website() default "example.com";
int version() default 1;
}
複製代碼
當註解須要參數的時候,咱們須要先定義註解的方法,方法名即參數名。這裏有點相似於接口的定義,若是參數須要默認值,則在方法後加default + 默認值
的方式來實現。
例如本例中,咱們指定了三個參數,name、website、version
。分別定義了三個方法,name
沒有指定默認值,因此它默認爲null,website
指定了默認值爲example.com
,而version
則指定了1爲其默認值。
這裏有三點規則強調一下:
name()
,website()
;default "example.com"
,默認website=」example.com」當咱們在使用時,須要給參數傳遞值,很簡單:
@TestAnno(name="xiaoming", website="example.com", version=1)
複製代碼
後文對這個例子中的其餘部分,還會有詳細的解釋。
爲了方便學習,咱們拿最多見的Java內建的註解@Override
來食用。把握一下自定義註解實現時的幾個步驟。
要自定義註解,首先要建立一個以註解名字命名的Java文件,並使用@interface關鍵字來定義註解
Override.java
package java.lang
public @interface Override {
}
複製代碼
這個也很容易理解,Override註解用來修飾方法,因此做用範圍就指定爲METHOD。
SOURCE、CLASS、RUNTIME的保留時限依次增長。而對於Override來講,咱們平常編寫代碼時,經過這個註解能夠知道哪些方法是被重寫的。這個提示,也僅僅停留在了源碼層面,因此這裏使用SOURCE。
Override並無使用任何的參數和方法,這裏也忽略了,後面咱們實戰例子裏會重點介紹。
基於上述分析,咱們寫出了以下的定義源碼:
package java.lang;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
複製代碼
經過以上的三個步驟,咱們就定義好了一個註解。當咱們使用時,直接經過@符號加註解名稱便可。那麼,加了註解的元素,有什麼用呢?
其一,咱們能夠經過編譯期處理,爲註解所在的元素自動生成其餘代碼。像ButterKnife
等均是如此,它幫助咱們生成了View與組件的綁定,實現了點擊監聽器的綁定等等。保留到此時限的註解,咱們也稱其爲編譯期註解。
其二,咱們在運行時,能夠經過反射,獲取到註解信息,這些註解信息,每每是程序編寫者但願傳遞給運行時使用的一些信息或配置,能夠起到簡化配置的做用。像Spring2.5
之後,運用了大量的運行時註解,它在實現AOP方面,有着普遍的應用。須要強調的是,這裏的註解,都是RUNTIME
規則的,只有這樣才能保留到運行時。因此咱們稱其爲運行時註解。
本篇咱們重點介紹代碼編寫階段的提示性註解和運行時註解。下面咱們再經過兩個實際例子來理解一下。
咱們在java中,除了Override
,還會常常見到一些其餘的內建註解。例如SuppressWarnings
(壓制警告),他用於告知編譯器忽略特定的警告信息,如在泛型中使用原始數據類型。
package java.lang;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,
ElementType.PARAMETER, ElementType.CONSTRUCTOR,
ElementType.LOCAL_VARIABLE })
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/** * The list of warnings a compiler should not issue. */
public String[] value();
}
複製代碼
按咱們上節所需編寫自定義註解的幾個步驟,來分別分析一下:
註解的做用範圍
SuppressWarnings
可用於除註解類型聲明和包名以外的全部元素。因此這裏的Target
作了相應的指定。
註解的做用時限
這裏的警告,都是靜態語法檢查類型的警告信息,因此這個註解也只須要保留在源碼層面。即SOURCE
。
註解的參數方法
SuppressWarning
指定了一個String
類型的數組。它支持了多個字符串參數。其可取值爲須要壓制的警告類型,見下表:
參數 | 含義 |
---|---|
deprecation | 使用了過期的類或方法時的警告 |
unchecked | 執行了未檢查的轉換時的警告 |
fallthrough | 當Switch程序塊進入進入下一個case而沒有Break時的警告 |
path | 在類路徑、源文件路徑等有不存在路徑時的警告 |
serial | 當可序列化的類缺乏serialVersionUID定義時的警告 |
finally | 任意finally子句不能正常完成時的警告 |
all | 以上全部狀況的警告 |
使用時,可按以下的方法賦值:
@SupressWarning(value={"uncheck","deprecation"})
複製代碼
針對咱們上一節中自定義的註解TestAnno,咱們來實踐一下運行時註解。
package com.xm.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnno{
String name();
String website() default "example.com";
int version() default 1;
}
複製代碼
假設咱們在以下的代碼塊中應用了此註解:
package com.xm.annotation;
public class AnnotationDemo {
@AuthorAnno(name="xiaoming", website="example.com", version=1)
public static void main(String[] args) {
System.out.println("I am main method");
}
@SuppressWarnings({ "unchecked", "deprecation" })
@AuthorAnno(name="suby", website="example2.com", version=2)
public void demo(){
System.out.println("I am demo method");
}
}
複製代碼
如今,咱們在運行時,經過反射來解析自定義的註解@TestAnno,關於反射類位於包java.lang.reflect,其中有一個接口AnnotatedElement,該接口定義了註釋相關的幾個核心方法,以下:
返回值 | 方法 | 含義 |
---|---|---|
T | getAnnotation(Class annotationClass) | 當存在該元素的指定類型註解,則返回相應註釋,不然返回null |
Annotation[] | getAnnotations() | 返回此元素上存在的全部註解 |
Annotation[] | getDeclaredAnnotations() | 返回直接存在於此元素上的全部註解 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 當存在該元素的指定類型註解,則返回true,不然返回false |
前面咱們自定義的註解,適用對象爲Method。類Method繼承類AccessibleObject,而類AccessibleObject實現了AnnotatedElement接口,那麼能夠利用上面的反射方法,來實現解析@TestAnno的功能(AnnotationParser.java),內容以下:
package com.xm.annotation;
import java.lang.reflect.Method;
public class AnnotationParser {
public static void main(String[] args) throws SecurityException, ClassNotFoundException {
String clazz = "com.xm.annotation.AnnotationDemo";
Method[] demoMethod = AnnotationParser.class
.getClassLoader().loadClass(clazz).getMethods();
for (Method method : demoMethod) {
if (method.isAnnotationPresent(TestAnno.class)) {
AuthorAnno authorInfo = method.getAnnotation(TestAnno.class);
System.out.println("method: "+ method);
System.out.println("name= "+ authorInfo.name() +
" , website= "+ authorInfo.website()
+ " , version= "+authorInfo.version());
}
}
}
}
複製代碼
程序的輸出結果:
method: public void com.xm.annotation.AnnotationDemo.demo()
name= suby , website= example2.com , version= 2
method: public static void com.xm.annotation.AnnotationDemo.main(java.lang.String[])
name= xiaoming , website= example.com , version= 1
複製代碼
因而可知,咱們能夠經過在編寫代碼時,將一些運行時須要的信息,經過註解的方式傳遞給運行時的代碼,以達到信息傳遞和配置的目的。在Spring中,也大量採用了運行時註解,爲程序的配置,以及程序開發,特別是AOP編程,都提供了極大的便利。
下一篇重點介紹編譯期註解,在衆多的知名框架或工具型SDK中,都能看見它的身影,它能夠以一種極度優雅簡潔的方式,爲咱們提供開發上的便捷。
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。