在java代碼中使用注釋是為了提升代碼的可讀性,也就是說注釋是給人看的(對于編譯器來說沒有意義)。注解可以看做是注釋的“強力升級版”,它可以向編譯器、虛擬機等傳遞一些信息(也就是說注解對編譯器等工具也是“可讀”的)。比如我們非常熟悉的@Override注解,它的作用是告訴編譯器它所注解的方法是重寫的父類中的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。
注解是描述Java代碼的代碼,它能夠被編譯器解析,注解處理工具在運行時能夠解析注解。
注解功能:
標記,用于告訴編譯器一些信息編譯時動態(tài)處理,如動態(tài)生成代碼運行時動態(tài)處理,如得到注解信息注解的語法和定義形式 (1)以@interface關(guān)鍵字定義 (2)注解包含成員,成員以無參數(shù)的方法的形式被聲明。其方法名和返回值定義了該成員的名字和類型。 (3)成員賦值是通過@Annotation(name=value)的形式。 (4)注解需要標明注解的生命周期,注解的修飾目標等信息,這些信息是通過元注解實現(xiàn)。
@Retention(value = RetentionPolicy.RUNTIME)@Target(value = { ElementType.ANNOTATION_TYPE } )public @interface Target{ ElementType[] value();}元注解Target 源碼分析如下: 第一:元注解@Retention,成員value的值為RetentionPolicy.RUNTIME。 第二:元注解@Target,成員value是個數(shù)組,用{}形式賦值,值為ElementType.ANNOTATION_TYPE 第三:成員名稱為value,類型為ElementType[] 另外,需要注意一下,如果成員名稱是value,在賦值過程中可以簡寫。如果成員類型為數(shù)組,但是只賦值一個元素,則也可以簡寫。如上面的簡寫形式為: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE)
可以使用注解來描述我們的意圖,然后讓注解解析工具來解析注解,以此來生成一些”模板化“的代碼。比如Hibernate、SPRing等框架大量使用了注解,來避免一些重復(fù)的工作。注解是一種”被動“的信息,必須由編譯器或虛擬機來“主動”解析它,它才能發(fā)揮自己的作用。
元注解用于定義注解時用來描述注解的注解,比如以下代碼中我們使用“@Target”元注解來說明MethodInfo這個注解只能用于對方法進行注解:
@Target(ElementType.METHOD) public @interface MethodInfo { ... }下面我們來具體介紹一下幾種元注解。 1. Retention 這個元注解表示一個注解的生命周期,有源碼階段,編譯器階段,運行階段,比如以下代碼表示Developer注解會被保留到運行時(也就是說在運行時依然能發(fā)揮作用):
@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時,后面括號里的內(nèi)容即表示它的取值,從以上定義我們可以看到,取值的類型為RetentionPolicy,這是一個枚舉類型,它可以取以下值:
SOURCE:表示注解只保留在源文件,當Java文件編譯成class文件的時候,注解被遺棄;CLASS:表示注解被保留到class文件,jvm加載class文件時候被遺棄。這是默認的生命周期。RUNTIME:表示注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在,保存到class對象中,可以通過反射來獲取。我們從以上代碼中可以看到,定義注解使用@interface關(guān)鍵字,這就好比我們定義類時使用class關(guān)鍵字,定義接口時使用interface關(guān)鍵字一樣,注解也是一種類型。關(guān)于@Documented和@Target的含義,下面進行介紹。
Documented當一個注解被@Documented元注解所修飾時,那么無論在哪里使用這個注解,都會被Javadoc工具文檔化。我們來看一下它的定義:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {}這個元注解被@Documented修飾,表示它本身也會被文檔化。@Retention元注解的值RetentionPolicy.RUNTIME表示Documented這個注解能保留到運行時;@Target元注解的值ElementType.ANNOTATION_TYPE表示Documented這個注解只能夠用來修飾注解類型。
Inherited表明被修飾的注解類型是自動繼承的。如果你想讓一個類和它的子類都包含某個注解,就可以使用@Inherited來修飾這個注解。也就是說,假設(shè)Parent類是Child類的父類,那么我們?nèi)粲帽籃Inherited元注解所修飾的某個注解對Parent類進行了修飾,則相當于Child類也被該注解所修飾了。這個元注解的定義如下:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)@Inheritedpublic @interface Inherited {}我們可以看到這個元注解類型被@Documented所注解,能夠保留到運行時,只能用來修飾注解類型。被注解的類他的子類自動被注解修飾
Target這個元注解說明了被修飾的注解的應(yīng)用對象(類,方法,字段,形參,構(gòu)造函數(shù),接口, 包等),也就是被修飾的注解可以用來注解哪些程序元素,它的定義如下:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target { ElementType[] value();}從以上定義我們可以看到它也會保留到運行時,而且它的取值是為ElementType[]類型(一個數(shù)組,意思是可以指定多個值),ElementType是一個枚舉類型,它可以取以下值:
TYPE:表示可以用于類、接口、注解類型或枚舉類型上的注解;PACKAGE:可以用于包上的注解;PARAMETER:可以用于參數(shù)的注解;ANNOTATION_TYPE:可以用來修飾注解類型;METHOD:可以用來修飾方法的注解;FIELD:可以用來修飾屬性(包括枚舉常量)注解;CONSTRUCTOR:可以用來修飾構(gòu)造器的注解;LOCAL_VARIABLE:可用來修飾局部變量。Java本身內(nèi)建了一些注解,下面我們來介紹一下我們在日常開發(fā)中比較常見的注解:@Override、@Deprecated、@SuppressWarnings。相信我們大家或多或少都使用過這三個注解,下面我們一起再重新認識一下它們。
@Override注解我們先來看一下這個注解類型的定義:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}從它的定義我們可以看到,這個注解只可以用來修飾方法,并且它只在編碼時有效,一旦進入編譯期就失效,在編譯后的class文件中便不再存在。這個注解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類中的相同簽名的方法,編碼時編譯器會對此做出檢查,若發(fā)現(xiàn)父類中不存在這個方法或是存在的方法簽名不同,則會報錯。
@Deprecated
這個注解的定義如下:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})public @interface Deprecated {}從它的定義我們可以知道,它會被文檔化,能夠保留到運行時,能夠修飾構(gòu)造方法、屬性、局部變量、方法、包、參數(shù)、類型。這個注解的作用是說明被修飾的程序元素已被“廢棄”,不再建議用戶使用。
@SuppressWarnings這個注解我們也比較常用到,先來看下它的定義:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { String[] value();}它能夠修飾的程序元素包括類型、屬性、方法、參數(shù)、構(gòu)造器、局部變量,只能存活在源碼時,取值為String[]。只在源碼中有效;它的作用是告訴編譯器忽略指定的警告信息,它可以取的值如下所示:
deprecation:忽略使用了廢棄的類或方法時的警告;unchecked:執(zhí)行了未檢查的轉(zhuǎn)換;fallthrough:swich語句款中case忘加break從而直接“落入”下一個case;path:類路徑或原文件路徑等不存在;serial:可序列化的類缺少serialVersionUID;finally:存在不能正常執(zhí)行的finally子句;all:以上所有情況產(chǎn)生的警告均忽略。這個注解的使用示例如下:
注解定義時候定義了一個函數(shù)value ,使用時候給他一個字符串數(shù)組,取值可以上面這些。
@SuppressWarning(value={"deprecation", "unchecked"})public void myMethos() {...}通過使用以上注解,我們告訴編譯器請忽略myMethod方法中“使用了廢棄的類或方法或是做了未檢查的轉(zhuǎn)換”而產(chǎn)生的警告。
我們可以創(chuàng)建我們自己的注解類型并使用它。請看下面的示例:
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)@Inheritedpublic @interface MethodInfo { String author() default "Vicent";//指定了默認值,也可以不指定 String date() default "2017/03/03"; int version() default 1;}在自定義注解時,有以下幾點需要我們了解:
注解類型是通過”@interface“關(guān)鍵字定義的; 在”注解體“中聲明注解成員,所有成員以無參數(shù)函數(shù)形式聲明;方法名和返回值類型表示成員名字和類型;沒有方法體且只允許public和abstract這兩種修飾符號(不加修飾符缺省為public),注解方法不允許有throws子句;注解方法的返回值只能為以下幾種:原始數(shù)據(jù)類型, String, Class, 枚舉類型, 注解和它們的一維數(shù)組,可以為方法指定默認返回值。
我們再把上面提到過的@SuppressWarnings這個注解類型的定義拿出來看一下,這個注解類型是系統(tǒng)為我們定義好的,它的定義如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { String[] value();}我們可以看到,它只定義了一個注解方法value(),它的返回值類型為String[],沒有指定默認返回值。我們使用@SuppressWarnings這個注解所用的語法如下:
@SuppressWarnings(value={"value1", "value2", ...})也就是在注解類型名稱后的括號內(nèi)為每個注解方法指定返回值就可以使用這個注解。由于@SuppressWarnings只包含一個注解方法,所以我們使用時可以也簡寫為“@SuppressWarnings(“value1”, “value2”)”。下面我們來看看怎么使用我們自定義的注解類型@MethodInfo:
我們使用的自定義注解對于編譯器或是虛擬機來說是有意義的嗎(編譯器或是虛擬機能讀懂嗎)?顯然我們什么都不做的話,編譯器是讀不懂我們的自定義注解的。下面我們介紹一下注解的解析,讓編譯器或虛擬機能夠讀懂我們的自定義注解。
編譯時注解指的是@Retention的值為CLASS的注解。對于這類注解的解析,我們只需做好以下兩件事兒:
自定義一個派生自 AbstractProcessor的“注解處理類”;重寫process 函數(shù)。實際上,javac中包含的注解處理器在編譯時會自動查找所有繼承自 AbstractProcessor 的類,然后調(diào)用它們的 process 方法。因此我們只要做好上面兩件事,編譯器就會主動去解析我們的編譯時注解。現(xiàn)在,我們把上面定義的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版本:8public 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 函數(shù)的annotations參數(shù)表示待處理的注解集,通過env我們可以得到被特定注解所修飾的程序元素。process函數(shù)的返回值表示annotations中的注解是否被這個Processor接受。
運行時注解解析首先我們把MethodInfo注解類型中Retention的值改回原來的RUNTIME,接下來我們介紹如何通過反射機制在運行時解析我們的自定義注解類型。
java.lang.reflect包中有一個AnnotatedElement接口,這個接口定義了用于獲取注解信息的幾個方法:
T getAnnotation(Class annotationClass) //返回該程序元素的指定類型的注解,若不存在這個類型的注解則返回nullAnnotation[] getAnnotations() //返回修飾該程序元素的所有注解Annotation[] getDeclaredAnnotations() //返回直接修飾該元素的所有注解boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) //當該程序元素被指定類型注解修飾時,返回true,否則返回false解析上面自定義注解MethodInfo在使用過程中使用的參數(shù)相關(guān)示例代碼如下:
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()); } } }}新聞熱點
疑難解答