Java中的註解是怎樣工作的

什麼是註解

用一個詞就可以描述註解,那就是元數據,即一種描述數據的數據。所以,可以說註解就是源代碼的元數據。比如,下面這段代碼:

@Override
public String toString() {
return "This is String Representation of current object.";
}

上面的代碼中,重寫了toString()方法並使用了@Override註解。

事實上,@Override告訴編譯器這個方法是一個重寫方法(描述方法的元數據),如果父類中不存在該方法,編譯器便會報錯,提示該方法沒有重寫父類中的方法。如果不小心拼寫錯誤,例如將toString()寫成了toStrring(){double r},而且也沒有使用@Override註解,那程序依然能編譯運行,但運行結果會和期望的大不相同。

Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。使用註解有助於閱讀程序。

爲什麼要引入註解

使用Annotation之前(甚至在使用之後),XML被廣泛的應用於描述元數據。不知何時開始一些應用開發人員和架構師發現XML的維護越來越糟糕了。他們希望使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是鬆耦合的(在某些情況下甚至是完全分離的)代碼描述。如果你在Google中搜索“XML vs. annotations”,會看到許多關於這個問題的辯論。最有趣的是XML配置其實就是爲了分離代碼和配置而引入的。上述兩種觀點可能會讓你很疑惑,兩者觀點似乎構成了一種循環,但各有利弊。下面我們通過一個例子來理解這兩者的區別。

假如你想爲應用設置很多的常量或參數,這種情況下,XML是一個很好的選擇,因爲它不會同特定的代碼相連。如果你想把某個方法聲明爲服務,那麼使用Annotation會更好一些,因爲這種情況下需要註解和方法緊密耦合起來,開發人員也必須認識到這點。

另一個很重要的因素是Annotation定義了一種標準的描述元數據的方式。在這之前,開發人員通常使用他們自己的方式定義元數據。例如,使用標記interfaces,註釋,transient關鍵字等等。每個程序員按照自己的方式定義元數據,而不像Annotation這種標準的方式。

註解是如何工作的

當註解標註到某個類或者方法或者某個成員變量或者某個輸入參數上的時候,一定有一個對應的機制來對註解標註的類、方法、成員變量和參數進行某些處理。

編寫Annotation非常簡單,註解本身也可以說是一個類。可以將Annotation的定義同接口的定義進行比較。看個例子,標準的註解@Override:

package java.lang;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

可以看見,@Override註解似乎什麼都沒做,那它是如何檢查在父類中有一個同名的函數?@Override註解的定義僅僅是元數據,不包含業務邏輯,而實現業務邏輯的就是註解的用戶。

對於@Override註解,它的用戶就是JVM虛擬機,工作在字節碼層面,在編譯階段進行檢查,其處理機制主要是JVM內部處理。

再例如Spring中的 @Service 註解,Spring在啓動IOC容器的時候會對每個類進行掃描,把所有標註@Component及其子註解如@Service的類進行Bean處理。

編寫自定義的註解

JDK5.0版本在 java.lang.annotation提供了四種元註解,專門註解其他的註解:

  • @Documented – 是否將註解信息添加在java文檔中
  • @Retention – 定義該註解的生命週期
  • @Target –註解用於什麼地方
  • @Inherited – 是否允許子類繼承該註解

@Retention 註解定義該註解的生命週期,在什麼階段丟棄,

  • RetentionPolicy.SOURCE – 在編譯階段丟棄。這些註解在編譯結束之後就不再有任何意義,所以它們不會寫入字節碼。@Override, @SuppressWarnings都屬於這類註解。
  • RetentionPolicy.CLASS – 在類加載的時候丟棄,在字節碼文件的處理中有用。註解默認使用這種方式。
  • RetentionPolicy.RUNTIME – 始終不會丟棄,運行期也保留該註解,因此可以使用反射機制讀取該註解的信息。自定義的註解通常使用這種方式。

@Target – 表示該註解用於什麼地方。如果不明確指出,該註解可以放在任何地方。屬性的註解是兼容的。

  • ElementType.TYPE:用於描述類、接口或enum聲明
  • ElementType.FIELD:用於描述實例變量
  • ElementType.METHOD
  • ElementType.PARAMETER
  • ElementType.CONSTRUCTOR
  • ElementType.LOCAL_VARIABLE
  • ElementType.ANNOTATION_TYPE 另一個註釋
  • ElementType.PACKAGE 用於記錄java文件的package信息

那麼,註解的內部到底是如何定義的呢?Annotations只支持基本類型、String及枚舉類型。註釋中所有的屬性被定義成方法,並允許提供默認值。

定義一個 @AuthCheck 註解,作用在SpringMVC controller的方法上。

package com.liuning.sys.auth;

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {
    String value() default "";
}

註解中只有一個屬性,可以直接命名爲“value”,使用時無需再標明屬性名。

在Spring的攔截器HandlerInterceptor中實現@AuthCheck註解的業務邏輯,這裏使用了反射機制。

@Override
public boolean preHandle(HttpServletRequest request, 
                         HttpServletResponse response, 
                         Object handler) throws Exception {
    HandlerMethod hm = (HandlerMethod) handler;
    AuthCheck ac = hm.getMethodAnnotation(AuthCheck.class);
    if (ac != null) {
        if (ac.value() == "Login") {
            //進行業務邏輯操作
        }
    }
    return true;
}

下面的例子演示瞭如何使用上面的註解。使用了該註解的方法在用戶調用的時候會需要用戶已登錄。

@PostMapping("/list")
@AuthCheck(value = "Login")
public List<User> getUserList(@RequestBody @Valid User user) {
    
    return userService.getUserList(user);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章