來源:編程無界

Java必知必會之註解

年輕人在家裏玩遊戲和流 playthrough 或步行

引子:全中國的Java程序員都知道,現在主流的項目架構都是ssm、ssh、springcloud等,而這些框架都離不開spring,而spring中使用了大量的註解(包括spring自定義的註解)。因此想要會用註解,我們就得知道Java註解的原理和基本用法,這樣有助於我們在項目中如魚得水。

在JDK5.0中,新增了很多對現在影響很大的特性,如:枚舉、自動裝箱和拆箱、註解、泛型等等。其中註解的引入,是爲了增加對元數據的支持。註解:是一個接口,程序可以通過反射來獲取指定程序元素的Annotation對象,然後通過Annotation對象來取得註解裏的元數據,註解能用來爲程序元素(包、類、方法、成員變量等)設置元數據,它不影響程序代碼的執行。如果希望讓程序中的Annotation在運行時起一定作用,只有通過某種配套的工具對Annotation中的信息進行訪問和處理,這個工具同城爲APT(Annotation program Tool)

JDK5.0除了增加註解特性之外,還提供了5個基本的Annotation:

  • @Overrride:限定重寫父類方法(旨在強制性提醒)
  • @Deprecated:表示某個程序元素(類、方法)已過時
  • @SuppressWarnings:抑制編譯警告
  • @SafeVarargs:堆污染警告
  • @FunctionalInterface:指定某個接口必須爲函數式接口
  • 注:函數式接口是指一個接口中只包含一個抽象方法(可以包含多個默認方法或多個static方法)

以上這幾個註解,在我們的項目經常可見,但是因爲他們對程序只是一個強制提醒或者警告作用,並不影響程序的執行,因爲我們都沒在意這些註解的作用,而當spring出來之後,大量的註解眼花繚亂,作用各異,但他們都有一個共同作用:

讓我們在編碼過程中簡化了不少重複性的代碼。

因此我們在瞭解了註解的原理之後,必須要能自定義一些註解並且使用它到項目中去,才能讓我們更好的瞭解和使用它。

而要想自定義註解,

就必須得了解Java提供的幾個元註解

那什麼是元註解呢?

元註解:就是負責註解其它註解的註解

在Java5之後定義了4個標準的元註解,分別是: 1. @Target 2. @Retention 3. @Documented 4. @Inherited

@Target

target註解用來標識註解所修飾的對象範圍。

它的可用範圍(ElementType的取值範圍)有:

  • CONSTRUCTOR:用於描述構造器(構造方法)
  • FIELD:用於描述域(成員變量)
  • LOCAL_VARIABLE:用於描述局部變量(局部變量)
  • METHOD:用於描述方法(普通方法)
  • PACKAGE:用於描述包(包定義)
  • PARAMETER:用於描述參數(如catch等參數)
  • TYPE:用於描述類、接口(包括註解類型) 或enum聲明
  • ANNOTATION_TYPE:用於註解

使用實例:

1@Target(ElementType.FIELD)
2public @interface TargetTest6{
3}
4@Target({ElementType.TYPE_PARAMETER,ElementType.METHOD})
5public @interface TargetTest7{
6}

@Retention

@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。

它的取值(RetentionPoicy)有:

  • SOURCE:在源文件中有效(即源文件保留),註解只保留在源代碼中,編譯器直接丟棄這種註解。
  • CLASS:在class文件中有效(即class保留),編譯器把註解記錄在class文件中,當Java程序運行時,JVM不能獲取該註解的信息。
  • RUNTIME:在運行時有效(即運行時保留),編譯器將把註解記錄在class文件中,當Java運行時,JVM可以獲取註解的信息,程序可以通過反射獲取該註解的信息。
1@Target(ElementType.FIELD)
2@Retention(RetentionPolicy.RUNTIME)
3public @interface TargetTest6{
4}

@Inherited

@Inherited註解指定被它休市的註解將具備繼承性:如果莫個類使用了@XXX註解,則其子類自動被@XXX修飾

1@Target(ElementType.FIELD)
2@Retention(RetentionPolicy.RUNTIME)
3@Inherited
4@interface TargetTest6{
5}
6//所有使用了@TargetTest6註解的類講具備繼承性,
7//也就是它的子類自動帶上@TargetTest6註解

@Documented

@Documented用於指定被該註解修飾的註解類將被javadoc工具提取城文檔.

1@Target(ElementType.FIELD)
2@Retention(RetentionPolicy.RUNTIME)
3@Inherited
4@Documented
5@interface TargetTest6{
6}
7//javadoc工具生成的API文檔將提取@Documented的使用信息

以上就是所有的元註解以及他們的作用分析,

有了這些元註解有什麼用呢?

。。。。

當然是爲了方便我們在項目中自定義註解咯

那自定義註解怎麼自定義呢?

下面我們來看看介紹如何自定義註解並利用註解完成一些實際的功能


語法:

1類修飾符 @interface 註解名稱{
2 //成員變量,在註解中以無形參的形式存在
3 //其方法名和返回值定義了該成員變的名字和類型
4 String name();
5 int age();
6}

可以看到:註解的語法和接口的定義非常類似,他也一樣具備有作用域,但是它的成員變量的定義是以無形參的方法形式存在,名字定義了成員變量的名字,返回值定義了變量類型。

下面我們來自定義一個註解:

1@Target(ElementType.FIELD)
2@Retention(RetentionPolicy.RUNTIME)
3public @interface AnonTest {
4 int age();
5 String name();
6}

註解@AnonTest在運行時有效,作用域在成員變量上,它有兩個成員變量分別是int型的age和String型的name。

接下來我們就可以使用這個註解了

1public class test { 
2 @AnonTest(age = 0, name = "1")
3 public Integer tes;
4}

這樣就是一個註解的定義和使用了,有人會疑惑說,spring中很多註解都是@xxx,爲什麼這個@AnonTest一定要要帶上兩個成員變量呢?

原因很簡單:

註解中的成員變量如果沒有默認值,則在使用註解時必須要給成員變量賦值

但如果成員變量有默認值,那可以直接在定義註解時,賦值上去,這樣在使用時就可以省略不寫

1@Target(ElementType.FIELD)
2@Inherited
3@Retention(RetentionPolicy.RUNTIME)
4public @interface AnonTest {
5 int age() default 1;
6 String name() default "註解辣雞";
7}

這樣在調用註解時就可以不賦值

1public class test {
2
3 @AnonTest
4 public Integer tes;

上面看到,我們已經使用了註解,但是我們並沒發現@AnonTest對我們的tes成員變量有任何作用,這是因爲註解本身在程序中是不會生效的,而是需要程序來提取數據並且處理註解本應該做的工作。

因此

我們需要知道如何從註解中提取信息並且做相應的處理。

Java使用Annotation接口來代表程序元素前面的註解,該接口是所有註解的父接口,該接口主要有以下幾個實現類:

  • Class:類定義
  • Constructor:構造器定義
  • Field:類成員變量定義
  • Method:類的方法定義
  • Package:類的包定義

AnnotatedElement接口是所有程序元素所實現的接口的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之後,程序就可以調用該對象的如下幾個方法來訪問註解信息:

  • getAnnotation(Class
  • annotationClass): 返回改程序元素上存在的、指定類型的註解,如果該類型註解不存在,則返回null。
  • getAnnotations():返回該程序元素上存在的所有註解。
  • isAnnotationPresent(Class annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,否則返回false.
  • getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。

有了這些方法,我們就可以在類、方法、成員變量等程序元素中獲取到註解信息

比如:

 1@Component
2@Service
3public class test {
4
5 @AnonTest
6 public Integer tes;
7
8 @Deprecated
9 public void m1(){
10
11 }
12
13 public static void main(String[] args) {
14 try {
15 //獲取所有test類上的註解
16 Annotation[] ar=Class.forName("com.anon.test").getAnnotations();
17 //獲取所有test類上m1方法的註解
18 Annotation[] ar1=Class.forName("com.anon.test").getMethod("m1").getAnnotations();
19 //遍歷所有的註解,檢測是否有我們想要的某個註解 如@Component註解
20 for(Annotation a:ar){
21 System.out.println(a.getClass());
22 if(a instanceof Component)
23 System.out.println("用了註解:"+a);
24 }
25 } catch (Exception e) {
26 // TODO Auto-generated catch block
27 e.printStackTrace();
28 }
29 }
30}
31
32//輸出:
33//用了註解:@org.springframework.stereotype.Component(value=)
34//class com.sun.proxy.$Proxy4

利用AnnotatedElement提供的方法能獲取到我們想要的註解信息之後,就可以針對註解做一些特定的行爲。

例如,對於所有的註冊用戶信息,系統需要把名稱和年齡上報到另一個年齡分佈表中,就可以利用註解就可以完成這樣的動作
 1public class test {
2 @AnonTest(name="張小龍",age=25)
3 public Integer tes;
4 public static void main(String[] args) {
5 test.getInfo(test.class);
6 }
7 public static void getInfo(Class> clazz) {
8 //獲取目標類的所有成員變量
9 Field[] fields = clazz.getDeclaredFields();
10 for (Field field : fields) {
11 //查找變量中是否有存在@AnonTest註解
12 if (field.isAnnotationPresent(AnonTest.class)) {
13 //如果存在,則做相應處理
14 AnonTest anon= field.getAnnotation(AnonTest.class);
15 System.out.println("名稱是:"+anon.name());
16 System.out.println("年齡是:"+anon.age());
17 System.out.println("上報信息到用戶年齡分佈表中");
18 //上報信息到用戶年齡分佈表中
19 //uploadInfoToLog(anon.name()),anon.age())
20 }
21 }
22 }
23}

輸出:

1名稱是:張小龍
2年齡是:25
3上報信息到用戶年齡分佈表中


最終程序按照我們的需求運行並且輸出正確的信息,由此可見,註解對於Java來說是一種補充,他的存在與否對程序來說應該是無影響的,靈活使用註解能使開發的效率更高,而且程序規範性也得到增強。

相關文章