1. 程式人生 > >由淺入深 帶你瞭解 JAVA 註解

由淺入深 帶你瞭解 JAVA 註解

在學習註解之前,我首先來講一講學習註解的好處,不管下面看不看,先打個雞血先。不過確定的是,在正常 JAVA 開發中,自己寫註解是比較少的,更多的情況就是使用第三方庫的註解,正因為如此,大多數開發者對於註解僅僅停留在會用的地步。試想一下,當大多數人都不會的時候你會,那麼你是不是就超越的大部分人。除此之外,我還總結學習註解的3大好處:

  1. 能夠讀懂別人寫的程式碼,特別是框架相關的程式碼;
  2. 讓程式設計更加簡潔,程式碼更加清晰;
  3. 讓別人高看一眼,裝逼利器;

說完了這些,下面就開始真乾貨了。

註解概念

JDK 5中引入了原始碼中的註解(annotation)這一機制。 註解使得Java原始碼中不但可以包含功能性的實現程式碼,還可以新增元資料。 註解的功能類似於程式碼中的註釋,所不同的是註解不是提供程式碼功能的說明,而是實現程式功能的重要組成部分。

Java中常見註解

對於常見註解,單單看在開發 JAVA 時 IDE 彈出的那些註解就能瞭解到,對於JDK 的註解,我們是要非常熟悉才行。

JDK自帶註解

  • @Override
  • @Deprecated
  • @Suppvisewarnings

常見第三方註解

  • Spring

    • @Autowired
    • @Service
    • @Repository
  • Mybatis

    • @InsertProvider
    • @UpdateProvider
    • @Options

註解的分類

按照執行機制分類

  • 原始碼註解 (註解只在原始碼中存在,編譯成.class檔案就不存在了)
  • 編譯時註解 (註解在原始碼和.class
    檔案中都存在,上面的3個JDK註解都是編譯時註解)
  • 執行時註解 (在執行階段還起作用,甚至會影響執行邏輯的註解,例如 @Autowired

按照來源分類

  • 來自JDK的註解
  • 來自第三方的註解
  • 自定義註解

元註解

給註解使用的註解

自定義註解

下面是一個典型的註解宣告,大家先看個大概,下面會對定義註解的注意事項做個說明:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface
Description {
String desc(); String author(); int age() default 18; }
  1. 使用@interface關鍵字定義註解
  2. 成員(變數)以無引數無異常方式宣告
  3. 可以用default為成員指定一個預設值
  4. 註解中成員的型別是受限制的,合法的型別包括基本資料型別以及 StringClassAnnotationEnumeration
  5. 如果註解只有一個成員,則成員名應該為value(),在使用時可以忽略成員名和賦值號(=
  6. 註解類可以沒有成員,沒有成員的註解稱為標識註解
  7. 元註解,用於對註解進行的註解,例如Description註解上面的4個註解

常用元註解

Target

標識註解的作用域,作用域有以下幾種,幾乎包含了 JAVA 所有的型別:
1. ElementType.CONSTRUCTOR:構造方法
2. ElementType.FIELD:欄位
3. ElementType.LOCAL_VARIABLE:區域性變數
4. ElementType.METHOD:方法
5. ElementType.PACKAGE:包
6. ElementType.PARAMETER: 引數
7. ElementType.TYPE:類、介面

例如上面例子中標識了Description可以用於對方法和類或介面進行註解:

@Target({ElementType.PARAMETER, ElementType.TYPE})

Retention

標識註解的生命週期,有以下3種值:
1. RetentionPolicy.SOURCE:只在原始碼顯示,編譯時會丟棄
2. RetentionPolicy.CLASS:編譯時會記錄到class檔案中,執行時會忽略
3. RetentionPolicy.RUNTIME:執行時存在,可以通過反射讀取

例如上面的例子中標識了Description可以記錄到class檔案中,但在執行時會忽略:

@Retention(RetentionPolicy.CLASS)

Inherited

標識性元註解,標識該註解允許子類進行繼承。注意這裡可不是註解的繼承,註解之間也沒有繼承什麼的。這裡指的是如果一個類(不是介面)宣告上使用了這個註解,那麼當它的一個子類繼承該類時,也會擁有與父類一樣擁有該註解。

Documented

標識在生成 JAVA DOC 時會包含該註解

使用自定義註解

使用註解的語法:

@<註解名>(<成員名1>=<成員值1>, <成員名1>=<成員值1>, <成員名1>=<成員值1>, ...)

其中的成員名則對應了註解裡的成員,例子:

@Description(desc="description", author="swifter", age=18)
public String getColor() {
    return "red";
}

上面只是個簡單的例子,下面給出一個註解的詳細使用方式,這也是在正常開發中會使用到的方式。對於註解,上面的程式碼中已經給出了一個宣告,下面就寫兩個類來使用該註解。通過這兩個例子,怎麼使用註解就顯而易見了。

第一個類 Person

@Description(desc="person interface", author="swifter")
public abstract class Person {
    @Description(desc="method getName", author="swifter")
    abstract String getName();
    abstract void doSomething();
}

第二個類 Child 繼承自 Person

public class Child extends Person {

    @Override
    @Description(desc="child method getName", author="swifter")
    public String getName() {
        return "get child";
    }

    @Override
    public void doSomething() {
        System.out.println("do something in child class");
    }
}

解析註解

既然已經定義了註解,那麼就應該考慮如何在程式碼中解析這個註解了。解析註解就是通過反射獲取類、函式或成員上的執行時註解資訊,從而實現動態控制程式執行的邏輯。
解析主要方式就是通過反射的途徑,通過Class物件來獲取註解,並拿到註解的欄位再進行操作。例如下面的例子:

Class<Child> clazz = Child.class;

if(clazz.isAnnotationPresent(Description.class)) {
    Description description = clazz.getAnnotation(Description.class);
    System.out.println(description.desc()+" : "+description.author());
}

Method[] methods = clazz.getMethods();
for(Method method : methods) {
    if(method.isAnnotationPresent(Description.class)) {
        Description description = method.getAnnotation(Description.class);
        System.out.println(description.desc()+" : "+description.author());
    }
}

執行之後客戶端會給出如下結果:

person interface : swifter
child method getName : swifter

其中第一行顯示的是類上面的註解資訊,第二行則顯示的是方法上的註解資訊。對於這兩個註解的物件,在程式碼中可以看到,都是通過反射的形式拿到的。雖然說反射的方式在效率上比較慢,但是想想註解帶來的優點,掌握註解還是有很大必要的。