一、什麼是註解(Annotation)
我們大家都知道Java代碼中使用註釋是爲了向以後閱讀這份代碼的人解釋說明一些事情,註解是註釋的升級版,它可以向編譯器、虛擬機等解釋說明一些事情。比如我們非常熟悉的@Override就是一種註解,它的作用是告訴編譯器它所註解的方法是重寫父類的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。註解是描述Java代碼的代碼,它能夠被編譯器解析,註解處理工具在運行時也能夠解析註解(具體的解析代碼在運行時通過反射和動態代理等方式來解析註解)。
註解的作用:
- 編寫文檔:通過代碼裏標識的元數據生成文檔。@Documented
- 代碼分析:通過代碼裏標識的元數據對代碼進行分析。
- 編譯檢查:通過代碼裏標識的元數據讓編譯器能實現基本的編譯檢查。@Override
註解作爲程序的元數據嵌入到程序。註解可以被解析工具或編譯工具解析,此處注意註解不同於註釋(comment)。
當一個接口直接繼承java.lang.annotation.Annotation接口時,仍是接口,而並非註解。要想自定義註解類型,只能通過@interface關鍵字的方式,其實通過該方式會隱含地繼承Annotation接口。
package java.lang.annotation;
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
/**
* Returns the annotation type of this annotation.
* @return the annotation type of this annotation
*/
Class<? extends Annotation> annotationType();
}
二、元註解
元註解即用來描述註解的註解,比如以下代碼中我們使用“@Target”元註解來說明MethodInfo這個註解只能應用於對方法進行註解:
@Target(ElementType.METHOD)
public @interface MethodInfo {
...
}
這些類型和它們所支持的類在java.lang.annotation包中可以找到。
1、@Target
@Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。
作用:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
2、@Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。
作用:表示需要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
我們在使用@Retention時,後面括號裏的內容即表示他的取值,從以上定義我們可以看到,取值的類型爲RetentionPolicy,這是一個枚舉類型,它可以取以下值:
SOURCE:表示在編譯時這個註解會被移除,不會包含在編譯後產生的class文件中,如@Override編譯器解析,編譯後移除;
CLASS:表示這個註解會被包含在class文件中即編譯時被保留,但在運行時會被忽略;
RUNTIME:表示這個註解會被保留到運行時,在運行時可以JVM訪問到,我們可以在運行時通過反射解析這個註解。
3、@Documented
Documented註解表明製作javadoc時,是否將註解信息加入文檔。
@Documented用於描述其它類型的annotation應該被作爲被標註的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。
作用:將註解包含在javadoc中
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
4、@Inherited
允許子類繼承父類的註解。
Inherited作用是,使用此註解聲明出來的自定義註解,在使用此自定義註解時,如果註解在類上面時,子類會自動繼承此註解,否則的話,子類不會繼承此註解。這裏一定要記住,使用Inherited聲明出來的註解,只有在類上使用時纔會有效,對方法,屬性等其他無效。
表明被修飾的註解類型是自動繼承的。具體解釋如下:若一個註解類型被Inherited元註解所修飾,則當用戶在一個類聲明中查詢該註解類型時,若發現這個類聲明中不包含這個註解類型,則會自動在這個類的父類中查詢相應的註解類型,這個過程會被重複,直到該註解類型被找到或是查找完了Object類還未找到。這個元註解的定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
三、常見內建註解
1. @Override註解
我們先來看一下這個註解類型的定義:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
從它的定義我們可以看到,這個註解可以被用來修飾方法,並且它只在編譯時有效,在編譯後的class文件中便不再存在。這個註解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類的中的相同簽名的方法,編譯器會對此做出檢查,若發現父類中不存在這個方法或是存在的方法簽名不同,則會報錯。
2. @Deprecated
這個註解的定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
從它的定義我們可以知道,它會被文檔化,能夠保留到運行時,能夠修飾構造方法、屬性、局部變量、方法、包、參數、類型。這個註解的作用是告訴編譯器被修飾的程序元素已被“廢棄”,不再建議用戶使用。
3. @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:以上所有情況產生的警告均忽略。
@SuppressWarning(value={"deprecation", "unchecked"})
public void myMethos() {...}
四、自定義註解
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;
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnno{
String name();
String website() default "git.com";
int revision() default 1;
}
規則
- 所有的方法均沒有方法體且只允許public和abstract這兩種修飾符號(不加修飾符缺省爲public),註解方法不允許有throws子句;
- 註解方法不帶參數
- 註解方法返回值類型只能用基本類型byte,short,int,long,float,double,boolean八種基本類型和String,Enum,Class,annotations及這些類型的數組
- 如果只有一個參數成員,最好將名稱設爲”value”
- 註解元素必須有確定的值,可以在註解中定義默認值,也可以使用註解時指定,非基本類型的值不可爲null,常使用空字符串或0作默認值
- 在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字符串或負值
註解的保留策略爲RetentionPolicy.RUNTIME,故可在運行期通過反射機制來使用,否則無法通過反射機制來獲取。
註解解析
Java使用Annotation接口來代表程序元素前面的註解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受註解的程序元素,該接口主要有如下幾個實現類:
Class:類定義
Constructor:構造器定義
Field:累的成員變量定義
Method:類的方法定義
Package:類的包定義
通過反射技術來解析自定義註解@AuthorAnno,關於反射類位於包java.lang.reflect,其中有一個接口AnnotatedElement,該接口定義了註釋相關的幾個核心方法,如下:
/***********註解聲明***************/
/**
* 水果名稱註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果顏色註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色枚舉
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};
/**
* 顏色屬性
* @return
*/
Color fruitColor() default Color.GREEN;
}
/**
* 水果供應者註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應商編號
* @return
*/
public int id() default -1;
/**
* 供應商名稱
* @return
*/
public String name() default "";
/**
* 供應商地址
* @return
*/
public String address() default "";
}
/***********註解使用***************/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陝西紅富士集團",address="陝西省西安市延安路89號紅富士大廈")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName(){
System.out.println("水果的名字是:蘋果");
}
}
/***********註解處理器***************/
//其實是用的反射
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){
String strFruitName=" 水果名稱:";
String strFruitColor=" 水果顏色:";
String strFruitProvicer="供應商信息:";
Field[] fields = clazz.getDeclaredFields();
for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供應商編號:"+fruitProvider.id()+" 供應商名稱:"+fruitProvider.name()+" 供應商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/***********輸出結果***************/
public class FruitRun {
/**
* @param args
*/
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
====================================
水果名稱:Apple
水果顏色:RED
供應商編號:1 供應商名稱:陝西紅富士集團 供應商地址:陝西省西安市延安路89號紅富士大廈
參考:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/
原理:http://blog.csdn.net/lylwo317/article/details/52163304