官方文檔是這麼描述註解的:java
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.程序員
Annotations have a number of uses, among them:shell
- Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
- Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
- Runtime processing — Some annotations are available to be examined at runtime.
簡單翻譯一下:註解是一種元數據,提供和程序相關的數據但不是程序自己的一部分,對他們註解的的代碼沒有直接影響。主要有幾種用途:爲編譯器提供信息、編譯時和部署時處理,運行時處理。編程
說實話,要第一次接觸註解,看到這樣的解釋,確定是雲裏霧裏的(天才請忽略),這他丫在說啥?元數據是啥?爲何能提供和程序有關的程序,又不是程序自己的一部分?.....數組
不如換一個思路,直接把註解當作標籤,標籤都知道吧,就是描述一種事物的東西,例如圖書館的書都貼有小條,該小條就是標籤,小條有不一樣的顏色,形狀,內容,這些就是標籤的屬性。從這個角度出發,咱們「從新定義」註解:註解是一種用來描述程序的標籤,對程序自己沒有直接影響,換句話說,即便給一本書貼上了標籤,也不會對書自己的內容有直接影響,書仍是那本書。框架
接下來我講介紹一些Java註解相關的內容,包括ide
註解能夠做用在類、方法、字段、接口甚至是註解上(還有其餘,後面會列出一個完整的列表),具體取決於註解是如何定義的。假設如今有有個@Yeonon註解,他能夠做用類、方法、字段上,那咱們能夠寫出這樣的程序:測試
@Yeonon
public class Main {
@Yeonon
private String name;
@Yeonon
public void testMethod1() {
System.out.println("test method 1");
}
public static void main(String[] args) {
}
}
複製代碼
使用起來就是那麼簡單,若是該註解有屬性,還能夠對屬性進行設置,爲編譯器或者運行時處理程序提供更多的信息,例如:spa
@Yeonon(value = "Main")
public class Main {
@Yeonon(value = "name")
private String name;
@Yeonon(value = "testMethod1")
public void testMethod1() {
System.out.println("test method 1");
}
public static void main(String[] args) {
}
}
複製代碼
關於如何使用就講到這吧,下面來介紹一下如何定義註解以及什麼是元註解。翻譯
註解經過@interface語法定義,以下所示:
public @interface Yeonon {
}
複製代碼
但光這樣定義註解,註解是沒法正常工做的,他沒有指明該註解可做用的元素類型,也沒有指明註解的做用時間範圍(即該註解在何時是生效的,何時是無效的),那如何指明呢?答案是使用元註解。
元註解即做用在註解上的註解,用來描述註解。若是把註解看作標籤,那麼元註解就是描述某個標籤的標籤,本質上仍然是一個標籤,只是他描述的對象是標籤,而普通標籤描述的是除標籤之外的事物。仍是有點繞,直接來看代碼吧:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.TYPE, ElementType.METHOD})
public @interface Yeonon {
}
複製代碼
代碼中的@Retention和@Target就是所謂的元註解,他們做用在註解上,更準確的說法是做用在註解定義上。之因此這樣說,是由於可能會有朋友將下面代碼所示的註解當作是元註解:
@Value
@Yeonon
private string name;
複製代碼
這裏@Value和@Yeonon都不是元註解,只是兩個註解同時做用在一個字段上而已,簡單理解就是一本書有兩個標籤。
Java中內置了5中元註解,分別是: @Retention、@Documented、@Target、@Inherited、@Repeatable 。
Retention翻譯過來就是保留期的意思,@Retention就是用來描述註解的保留時間的,具體的保留時間根據其value屬性來肯定,其value屬性是RetentionPolicy類型的值,該類型有以下幾種取值:
Documented翻譯過來就是文檔的意思。做用是將註解的元素包含到Java doc中。
Target翻譯過來是目標的意思,@Target的做用是指定該註解做用的地方,例如方法、字段、接口。能夠有以下取值:
Inherited翻譯過來是繼承的意思,但並非指註解能夠被繼承,而是指的若是一個類被@Inherited註解做用的註解進行註解,那麼其子類也會被該註解做用。有點繞,直接來看代碼吧:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.TYPE, ElementType.METHOD})
@Inherited
public @interface Yeonon {
}
@Yeonon
public class Base {
//...
}
public class Sub extends Base {
//...
}
複製代碼
@Yeonon上有@Inherited註解,而後@Yeonon做用到Base類上,而Sub類是Base類的子類,那麼Sub類默認就也有@Yeonon註解。
Repeatable翻譯過來是可重複的意思,這是Java8新增的元註解,那這個註解有什麼用呢?先來看一個場景,假設一我的既是程序員、又是產品經理(舉個例子而已,哈哈)、又是老闆,如今有一個@Identity註解,以下所示:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
public @interface Identity {
String role();
}
複製代碼
可能會這樣使用註解來描述一我的:
@Identity(role = "coder")
@Identity(role = "pm")
private User user;
複製代碼
編譯一下,發現沒法經過編譯,錯誤提示大體是,該註解類型是不可重複的(Java8)。在Java7以前,可能就會定義一個新的能夠容納多個元素的註解來解決這個問題,以下所示:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
public @interface Identites {
Identity[] value();
}
@Identites(roles = {@Identity(role = "coder"), @Identity(role = "pm")})
private User user;
複製代碼
這樣作能夠解決問題,但可讀性並很差,並且會給註解處理程序帶來麻煩。在Java8中,可使用@Repeatable元註解來表示註解能夠重複使用,以下所示:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Repeatable(value = Identites.class)
public @interface Identity {
String role();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
public @interface Identites {
Identity[] value();
}
//使用
@Identity(role = "coder")
@Identity(role = "pm")
@Identity(role = "boss")
private User user;
複製代碼
可讀性好了不少,咱們看一眼就知道這我的有三個身份,coder,pm和boss。但仍然須要一個新的註解(例子中的@Identites)來容納多個元素,這種類型的註解被稱做「容器註解」。
除了上述的5個內置的元註解,實際上咱們還能夠自定義元註解,還記得以前講@Target註解的時候,ElementType類型有一個ANNOTATION_TYPE屬性嗎?在@Target的value屬性的集合中加入這個類型,就表示該註解是一個元註解了,但最好不用過分使用該功能,由於可能會致使一些邏輯混亂。
在上面的代碼中,其實已經出現過屬性了,例如以前定義的@Identity註解,該註解有一個String類型的屬性,名字是role,以下所示:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Repeatable(value = Identites.class)
public @interface Identity {
String role();
}
複製代碼
發現這個和聲明類的字段有些不同,比聲明字段多了一個括號。這是註解的語法,至於爲何非要這樣搞,我也不太明白。屬性的類型能夠是8中基本類型即其數組類型、引用類型和註解類型,若是聲明屬性的時候,沒有default值,在使用註解的時候就必須給該屬性賦值。例如上面的role屬性,由於沒有默認值,因此在使用的時候必須給出role的值,那默認值該如何定義呢?直接來看代碼:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Repeatable(value = Identites.class)
public @interface Identity {
String role();
String name() default "Identity";
}
複製代碼
即在屬性聲明以後加入default關鍵字和默認值,很是簡單。
那這些屬性有什麼用呢?能夠這樣簡單的理解:屬性提供了額外的信息。舉個例子,若是@Identity沒有屬性(這種註解稱做標記註解),當他做用在某個地方的時候,程序包括咱們人類都僅僅能知道該註解有一個身份,但不知道具體是什麼身份,爲了讓程序和人類能知道具體是什麼身份,就須要用到屬性了,例如上述的role屬性,此時再使用@Identity的時候,就能夠添加role屬性的值,用來表示具體的身份,例如coder,pm等。
下面來介紹一下註解和反射的結合,若是這裏對屬性仍是有些不明白也不要緊,下面的介紹會加深對屬性的理解。
上面講了那麼多,你是否有一個疑問:程序是如何從這些註解中提取信息的?答案就是結合反射(關於反射,我在以前的文章有說過,在此就直接使用了,再也不講原理),經過反射獲取到類、字段、方法上的註解,而後對註解進行解析並做出相應的處理。下面我將經過一個簡單的測試框架來演示註解如何和反射結合使用。
首先,先編寫一個註解,當某個方法上有該註解時,就表示該方法應該被測試執行,以下所示:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface YTest {
//暫時不須要屬性
}
複製代碼
而後,開始編寫測試框架:
public class MyTestTool {
public static void main(String[] args) throws ClassNotFoundException {
//獲取待測試類的類對象
Class<?> testClass = Class.forName("top.yeonon.anotation.MyTest");
int passed = 0; //經過測試的數量
int tested = 0; //測試的數量
//獲取待測試類全部方法
Method[] methods = testClass.getDeclaredMethods();
for (Method method : methods) {
//若是還方法上有YTest註解,就對其作處理,不然就直接跳過
if (method.isAnnotationPresent(YTest.class)) {
tested++;
try {
method.invoke(null);
passed++; //能走到這,說明沒有發生異常,即測試經過
} catch (InvocationTargetException e) {
System.out.println(method.getName() + " test failed!");
} catch (Exception e) {
System.out.println("Invalid test : " + method.getName());
}
}
}
System.out.println("tested : " + tested);
System.out.println("Passed : " + passed);
System.out.println("Failed : " + (tested - passed));
System.out.println("Passed Rate : " + ((double)passed / (double)tested));
}
}
複製代碼
該測試框架很是簡單粗暴,最核心的邏輯是經過反射來判斷方法上是否有@YTest註解,若是沒有,直接跳過該方法,不計入測試總數裏,若是有,就調用invoke()方法執行該方法,由於該框架只對靜態方法作測試,因此invoke方法的參數是null。若是拋出異常則表示失敗(實際上真正的測試框架不會那麼簡單),若是沒有拋出異常則表示成功,最後打印輸出一些信息做爲結果報告。
最後,編寫待測試類,以下所示:
public class MyTest {
@YTest
public void m1() {
//do something
}
@YTest
public void m2() {
//拋出異常來表示測試失敗
throw new RuntimeException();
}
public void m3() {
}
@YTest
public static void m4() {
}
@YTest
public static void m5() {
throw new RuntimeException();
}
public static void m6() {
}
}
複製代碼
一共有6個方法,3個實例方法,3個靜態方法,只有4個方法有@YTest註解,其中有兩個方法拋出異常,分別是m2和m5。先來來運行一下以前寫的測試框架程序吧,運行結果大體以下所示:
m5 test failed!
Invalid test : m1
Invalid test : m2
tested : 4
Passed : 1
Failed : 3
Passed Rate : 0.25
複製代碼
能夠看到,m5測試失敗,由於m5拋出了異常,m1和m2則是非法測試,由於m1和m2是實例方法,即便m2內部也拋出了異常,但實際上再執行以前就已經出現了InvocationTargetException,該異常先發生,根本不會調用m2。最後幾行表示共有4個測試用例,經過了1個,失敗了3個,經過率是25%。
一個小型的測試框架就算是完成了,雖然簡單粗糙,但做爲演示反射和註解的結合已經徹底足夠了,相信有了上面的介紹,對於如何將反射和註解結合在一塊兒,你已經大概明白了。
本文簡單介紹了什麼是註解、元註解、註解的使用以及反射和註解結合使用。註解是Java5提供的一個強大的特性,不少框架例如JUnit四、Spring家族的產品例如Spring Boot,Spring Cloud系列都大量的使用註解來簡化編程,可見註解的功能是多麼強大,並且Java8中還新增了不少和註解有關的東西,從這也能夠看出,Java官方也在大量發展註解。