java強大靈活的註解學習筆記

一、什麼是註解

在Java代碼中使用註釋是爲了提升代碼的可讀性,也就是說註釋是給人看的(對於編譯器來說沒有意義)。註解可以看做是註釋的“強力升級版”,它可以向編譯器、虛擬機等傳遞一些信息(也就是說註解對編譯器等工具也是“可讀”的)。比如我們非常熟悉的@Override註解,它的作用是告訴編譯器它所註解的方法是重寫的父類中的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。

註解是描述Java代碼的代碼,它能夠被編譯器解析,註解處理工具在運行時能夠解析註解。

註解功能:

  • 標記,用於告訴編譯器一些信息
  • 編譯時動態處理,如動態生成代碼
  • 運行時動態處理,如得到註解信息

註解的語法和定義形式
(1)以@interface關鍵字定義
(2)註解包含成員,成員以無參數的方法的形式被聲明。其方法名和返回值定義了該成員的名字和類型。
(3)成員賦值是通過@Annotation(name=value)的形式。
(4)註解需要標明註解的生命週期,註解的修飾目標等信息,這些信息是通過元註解實現。

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE } )
public @interface Target
{
    ElementType[] value();
}

元註解Target 源碼分析如下:
第一:元註解@Retention,成員value的值爲RetentionPolicy.RUNTIME。
第二:元註解@Target,成員value是個數組,用{}形式賦值,值爲ElementType.ANNOTATION_TYPE
第三:成員名稱爲value,類型爲ElementType[]
另外,需要注意一下,如果成員名稱是value,在賦值過程中可以簡寫。如果成員類型爲數組,但是隻賦值一個元素,則也可以簡寫。如上面的簡寫形式爲:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)

可以使用註解來描述我們的意圖,然後讓註解解析工具來解析註解,以此來生成一些”模板化“的代碼。比如Hibernate、Spring等框架大量使用了註解,來避免一些重複的工作。註解是一種”被動“的信息,必須由編譯器或虛擬機來“主動”解析它,它才能發揮自己的作用

二、元註解

元註解用於定義註解時用來描述註解的註解,比如以下代碼中我們使用“@Target”元註解來說明MethodInfo這個註解只能用於對方法進行註解:

@Target(ElementType.METHOD)
    public @interface MethodInfo { 
        ...
    }   

下面我們來具體介紹一下幾種元註解。
1. Retention
這個元註解表示一個註解的生命週期,有源碼階段,編譯器階段,運行階段,比如以下代碼表示Developer註解會被保留到運行時(也就是說在運行時依然能發揮作用):

@Retention(RetentionPolicy.RUNTIME)
public @interface Developer {
    String value();
}

@Retention元註解的定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

我們在使用@Retention時,後面括號裏的內容即表示它的取值,從以上定義我們可以看到,取值的類型爲RetentionPolicy,這是一個枚舉類型,它可以取以下值:

  • SOURCE:表示註解只保留在源文件,當Java文件編譯成class文件的時候,註解被遺棄;
  • CLASS:表示註解被保留到class文件,jvm加載class文件時候被遺棄。這是默認的生命週期。
  • RUNTIME:表示註解不僅被保存到class文件中,jvm加載class文件之後,仍然存在,保存到class對象中,可以通過反射來獲取。

我們從以上代碼中可以看到,定義註解使用@interface關鍵字,這就好比我們定義類時使用class關鍵字,定義接口時使用interface關鍵字一樣,註解也是一種類型。關於@Documented和@Target的含義,下面進行介紹。

  1. Documented

當一個註解被@Documented元註解所修飾時,那麼無論在哪裏使用這個註解,都會被Javadoc工具文檔化。我們來看一下它的定義:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

這個元註解被@Documented修飾,表示它本身也會被文檔化。@Retention元註解的值RetentionPolicy.RUNTIME表示Documented這個註解能保留到運行時;@Target元註解的值ElementType.ANNOTATION_TYPE表示Documented這個註解只能夠用來修飾註解類型。

  1. Inherited

表明被修飾的註解類型是自動繼承的。如果你想讓一個類和它的子類都包含某個註解,就可以使用@Inherited來修飾這個註解。也就是說,假設Parent類是Child類的父類,那麼我們若用被@Inherited元註解所修飾的某個註解對Parent類進行了修飾,則相當於Child類也被該註解所修飾了。這個元註解的定義如下:


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@Inherited
public @interface Inherited {
}

我們可以看到這個元註解類型被@Documented所註解,能夠保留到運行時,只能用來修飾註解類型。被註解的類他的子類自動被註解修飾

  1. Target

這個元註解說明了被修飾的註解的應用對象(類,方法,字段,形參,構造函數,接口, 包等),也就是被修飾的註解可以用來註解哪些程序元素,它的定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Target {
    ElementType[] value();
}

從以上定義我們可以看到它也會保留到運行時,而且它的取值是爲ElementType[]類型(一個數組,意思是可以指定多個值),ElementType是一個枚舉類型,它可以取以下值:

  • TYPE:表示可以用於類、接口、註解類型或枚舉類型上的註解;
  • PACKAGE:可以用於包上的註解;
  • PARAMETER:可以用於參數的註解;
  • ANNOTATION_TYPE:可以用來修飾註解類型;
  • METHOD:可以用來修飾方法的註解;
  • FIELD:可以用來修飾屬性(包括枚舉常量)註解;
  • CONSTRUCTOR:可以用來修飾構造器的註解;
  • LOCAL_VARIABLE:可用來修飾局部變量。

三、常見內建註解

Java本身內建了一些註解,下面我們來介紹一下我們在日常開發中比較常見的註解:@Override、@Deprecated、@SuppressWarnings。相信我們大家或多或少都使用過這三個註解,下面我們一起再重新認識一下它們。

  1. @Override註解

我們先來看一下這個註解類型的定義:

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

從它的定義我們可以看到,這個註解只可以用來修飾方法,並且它只在編碼時有效,一旦進入編譯期就失效,在編譯後的class文件中便不再存在。這個註解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類中的相同簽名的方法,編碼時編譯器會對此做出檢查,若發現父類中不存在這個方法或是存在的方法簽名不同,則會報錯。

  1. @Deprecated

    這個註解的定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

從它的定義我們可以知道,它會被文檔化,能夠保留到運行時,能夠修飾構造方法、屬性、局部變量、方法、包、參數、類型。這個註解的作用是說明被修飾的程序元素已被“廢棄”,不再建議用戶使用。

  1. @SuppressWarnings

這個註解我們也比較常用到,先來看下它的定義:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
  String[] value();
}

它能夠修飾的程序元素包括類型、屬性、方法、參數、構造器、局部變量,只能存活在源碼時,取值爲String[]。只在源碼中有效;它的作用是告訴編譯器忽略指定的警告信息,它可以取的值如下所示:

  • deprecation:忽略使用了廢棄的類或方法時的警告;
  • unchecked:執行了未檢查的轉換;
  • fallthrough:swich語句款中case忘加break從而直接“落入”下一個case;
  • path:類路徑或原文件路徑等不存在;
  • serial:可序列化的類缺少serialVersionUID;
  • finally:存在不能正常執行的finally子句;
  • all:以上所有情況產生的警告均忽略。

這個註解的使用示例如下:

註解定義時候定義了一個函數value ,使用時候給他一個字符串數組,取值可以上面這些。

@SuppressWarning(value={"deprecation", "unchecked"})
public void myMethos() {...} 

通過使用以上註解,我們告訴編譯器請忽略myMethod方法中“使用了廢棄的類或方法或是做了未檢查的轉換”而產生的警告。

四、自定義註解

我們可以創建我們自己的註解類型並使用它。請看下面的示例:

package org.vincent.maven.annotation.anno;

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;

/**
 * 自定義註解
 * 
 * @author PengRong
 *
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {

    String author() default "Vicent";//指定了默認值,也可以不指定

    String date() default "2017/03/03";

    int version() default 1;
}

在自定義註解時,有以下幾點需要我們瞭解:

註解類型是通過”@interface“關鍵字定義的;
在”註解體“中聲明註解成員,所有成員以無參數函數形式聲明;方法名和返回值類型表示該自定義註解屬性名字和屬性可設置值類型;沒有方法體且只允許public和abstract這兩種修飾符號(不加修飾符缺省爲public),註解方法不允許有throws子句;註解方法的返回值只能爲以下幾種:原始數據類型, String, Class, 枚舉類型, 註解和它們的一維數組,可以爲方法指定默認返回值。

我們再把上面提到過的@SuppressWarnings這個註解類型的定義拿出來看一下,這個註解類型是系統爲我們定義好的,它的定義如下:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

我們可以看到,它只定義了一個註解方法value(),它的返回值類型爲String[],沒有指定默認返回值。我們使用@SuppressWarnings這個註解所用的語法如下:

@SuppressWarnings(value={"value1", "value2", ...})也就是在註解類型名稱後的括號內爲每個註解方法指定返回值就可以使用這個註解。由於@SuppressWarnings只包含一個註解方法,所以我們使用時可以也簡寫爲“@SuppressWarnings(“value1”, “value2”)”。下面我們來看看怎麼使用我們自定義的註解類型@MethodInfo:

package org.vincent.maven.annotation;

import org.vincent.maven.annotation.anno.MethodInfo;

/**
 * Hello world!
 *
 */
public class App {
    @MethodInfo(author = "Cindy", date = "day", version = 2)
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

我們使用的自定義註解對於編譯器或是虛擬機來說是有意義的嗎(編譯器或是虛擬機能讀懂嗎)?顯然我們什麼都不做的話,編譯器是讀不懂我們的自定義註解的。下面我們介紹一下註解的解析,讓編譯器或虛擬機能夠讀懂我們的自定義註解。

五、註解的解析

  1. 編譯時解析

編譯時註解指的是@Retention的值爲CLASS的註解。對於這類註解的解析,我們只需做好以下兩件事兒:

  • 自定義一個派生自 AbstractProcessor的“註解處理類”;
  • 重寫process 函數。實際上,javac中包含的註解處理器在編譯時會自動查找所有繼承自 AbstractProcessor 的類,然後調用它們的 process 方法。因此我們只要做好上面兩件事,編譯器就會主動去解析我們的編譯時註解。現在,我們把上面定義的MethodInfo的Retention改爲CLASS,我們就可以按照以下代碼來解析它:
package org.vincent.maven.annotation.anno;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

//SupportedAnnotationTypes指出了MyProcessor將要解析的註解的完整名字(全限定名稱)
//註釋處理器支持的註釋:MethodInfo
@SupportedAnnotationTypes({ "org.vincent.maven.annotation.anno.MethodInfo" })
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 註釋處理器支持的JDK版本:8
public class MyProcess extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        // TODO Auto-generated method stub
        super.init(processingEnv);
        System.out.println(processingEnv.toString());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        Map<String, String> map = new HashMap<String, String>();

        for (TypeElement e : annotations) {
            MethodInfo mi = e.getAnnotation(MethodInfo.class);
            map.put(e.getEnclosingElement().toString(), mi.author());
            System.out.println(map);
            System.out.println("xxxx");
        }
        if (!roundEnv.processingOver()) {
            processingEnv.getMessager().printMessage( // 註釋處理器的報告
                    Diagnostic.Kind.NOTE, "Hello World MethodInfo!");
        }
        return true;
    }

}

@SupportedAnnotationTypes註解指出了MyProcess向要解析的註解的完整名字(全限定名稱)。process 函數的annotations參數表示待處理的註解集,通過env我們可以得到被特定註解所修飾的程序元素。process函數的返回值表示annotations中的註解是否被這個Processor接受。

  1. 運行時註解解析

首先我們把MethodInfo註解類型中Retention的值改回原來的RUNTIME,接下來我們介紹如何通過反射機制在運行時解析我們的自定義註解類型。

java.lang.reflect包中有一個AnnotatedElement接口,這個接口定義了用於獲取註解信息的幾個方法:

T getAnnotation(Class annotationClass) //返回該程序元素的指定類型的註解,若不存在這個類型的註解則返回null
Annotation[] getAnnotations() //返回修飾該程序元素的所有註解
Annotation[] getDeclaredAnnotations() //返回直接修飾該元素的所有註解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) //當該程序元素被指定類型註解修飾時,返回true,否則返回false

解析上面自定義註解MethodInfo在使用過程中使用的參數相關示例代碼如下:


package org.vincent.maven.annotation;

import java.lang.reflect.Method;

import org.vincent.maven.annotation.anno.MethodInfo;

public class AnnotationParser {

    public static void main(String[] args) {
        Class<? extends App> class1 = App.class;
        for (Method method : class1.getMethods()) {
            MethodInfo methodInfo;
            methodInfo = method.getAnnotation(MethodInfo.class);// 返回註解這個方法的指定註解
            if (methodInfo != null) {
                System.out.println(method.getName());
                System.out.println(methodInfo.author());// 獲取指定註解指定方法的值
                System.out.println(methodInfo.date());
                System.out.println(methodInfo.version());

            }
        }

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章