先來看一個Java註解的思維腦圖
一 註解(Annotation)基本概念和分類
1.1 註解(Annotation)基本概念
Annotion(註解)概念:Java 提供了一種原程序中的元素關聯任何信息和任何元數據的途徑和方法。概念有點難懂,等會看具體的例子。
Annotion(註解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然後通過Annotion對象來獲取註解裏面的元數據。
Annotation(註解)是JDK5.0及以後版本引入的。
Annotation(註解)基本的規則:Annotation不能影響程序代碼的執行,無論增加、刪除 Annotation,代碼都始終如一的執行。
1.2 註解(Annotation)分類
按照運行機制分類:
1. 源碼註解 : 註解只在源碼中存在,編譯成.class 文件就不存在了。
2. 編譯時註解 : 註解在源碼和.class文件中都存在。
3. 運行時註解 : 在運行階段還起作用,甚至還會影響運行邏輯的註解。
按照註解來源分類:
1. JDK自帶註解
2. 第三方註解
3. 自定義註解
1.3 元數據(metadata)基本概念和作用
元數據概念:元數據從metadata一詞譯來,就是“關於數據的數據”的意思。
元數據的功能作用:
1. 編寫文檔:通過代碼裏標識的元數據生成文檔(比如:你可能用過Javadoc的註釋自動生成文檔。這就是元數據功能的一種)。
2. 代碼分析:通過代碼裏標識的元數據對代碼進行分析。
3. 編譯檢查:通過代碼裏標識的元數據讓編譯器能實現基本的編譯檢查。
在Java中元數據以標籤的形式存在於Java代碼中,元數據標籤的存在並不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或針在運行時知道被運行代碼的描述信息。
綜上所述:
第一,元數據以標籤的形式存在於Java代碼中。
第二,元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。
第三,元數據需要編譯器之外的工具額外的處理用來生成其它的程序部件。
第四,元數據可以只存在於Java源代碼級別,也可以存在於編譯之後的Class文件內部。
二 系統註解之標準註解和元註解
註解的語法比較簡單,除了@符號的使用外,他基本與Java固有的語法一致。
JDK中內置三個標準註解,定義在java.lang中:
1. @Override:用於修飾此方法覆蓋了父類的方法;
2. @Deprecated:用於修飾已經過時的方法;
3. @SuppressWarnnings:用於通知java編譯器禁止特定的編譯警告。
元註解的作用就是負責註解其他註解。
Java定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型作說明。4個元註解如下:
1. @Target
2. @Retention
3. @Documented
4. @Inherited
先來看個例子
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Table {
String value();
}
下面我們看一下每個元註解的作用和相應分參數的使用說明。
2.1 @Target
@Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。
作用:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)
取值(ElementType)有:
1. CONSTRUCTOR:用於描述構造器
2. FIELD:用於描述字段聲明
3. LOCAL_VARIABLE:用於描述局部變量
4. METHOD:用於描述方法
5. PACKAGE:用於描述包
6. PARAMETER:用於描述參數
7. TYPE:用於描述類、接口(包括註解類型) 或enum聲明
上面的例子 註解Table 用於註解類、接口(包括註解類型) 或enum聲明
2.2 @Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。
作用:表示需要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
取值(RetentionPoicy)有:
1. SOURCE:在源文件中有效(即源文件保留)。
2. CLASS:在class文件中有效(即class保留)。
3. RUNTIME:在運行時有效(即運行時保留)。
上面的例子 註解Table的RetentionPolicy屬性值是RUNTIME,即在運行時有效。註解處理器可以通過反射,獲取到該註解的屬性值,從而去做一些運行時的邏輯處理。
2.3 @Documented
@Documented用於描述其它類型的annotation應該被作爲被標註的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。
2.4 @Inherited
@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
注意:@Inherited annotation類型是被標註過的class的子類所繼承。類並不從它所實現的接口繼承annotation,方法並不從它所重載的方法繼承annotation。
當@Inherited annotation類型標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。
三 自定義註解和註解使用
定義註解格式:
public @interface 註解名 {定義體}
註解參數的可支持數據類型:
- 所有基本數據類型(int,float,boolean,byte,double,char,long,short)
- String類型
- Class類型
- enum類型
- Annotation類型
- 以上所有類型的數組
、
使用註解的語法:
@<註解名>(<成員名1>=<成員值1>,<成員名2>=<成員值2>,.......)
@Description(des = “zhang”,anthor=“qilu”,age=20)
public String eyeColor(){
return “red"
}
自定義註解和使用註解注意事項:
1. 使用@interface 關鍵字定義註解。
2. 成員以無參無異常方式聲明。
3. default爲成員指定一個默認值。
4. 成員類型是受限的,合法的類型包括原始類型及String,Class,Annotation,enum,和以上類型的數組。
5. 如果註解只有一個成員,成員名字必須取名爲value(),在使用時可以忽略成員名和賦值號(=)。
6. 註解類可以沒有成員,沒有成員的註解稱爲標誌註解。
四 解析註解
基本概念:通過反射獲取類,函數或者成員上的運行時註解信息,從而實現動態控制程序運行的邏輯。
java.lang.reflect 包下主要包含一些實現反射功能的工具類,擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲運行時的Annotation後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之後,程序就可以調用該對象的如下四個個方法來訪問Annotation信息:
方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回該程序元素上存在的、指定類型的註解,如果該類型註解不存在,則返回null。
方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有註解。
方法3:boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,否則返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此接口中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。
下面我們來看個例子 ,模擬查詢數據庫
//Column 註解
@Target({ElementType.FIELD})//字段聲明
@Retention(RetentionPolicy.RUNTIME)//運行時存在,可以通過反射讀取
public @interface Column {
String value();
}
//Table 註解
@Target({ElementType.TYPE})//類、接口(包括註解類型) 或enum聲明
@Retention(RetentionPolicy.RUNTIME)//運行時存在,可以通過反射讀取
public @interface Table {
String value();
}
//Filter類使用了 Table 和 Column 註解
@Table("user")
public class Filter {
@Column("id")
private int id;
@Column("userName")
private String userName;
@Column("nickName")
private String nickName;
@Column("age")
private int age;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("mobile")
private String mobile;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
// 實現3個Filter實例
Filter f1 = new Filter();
f1.setId(10);//查詢ID爲10的用戶
Filter f2 = new Filter();
f2.setUserName("lucy");
Filter f3 = new Filter();
f3.setEmail("[email protected]");
Log.e(TAG, "onCreate: "+query(f1) );
Log.e(TAG, "onCreate: "+query(f2) );
Log.e(TAG, "onCreate: "+query(f3) );
//註解解析 通過反射
private String query(Filter filter){
StringBuilder stringBuilder = new StringBuilder();
//獲取Class
Class c = filter.getClass();
//2. 獲取talbe的名字
if(!c.isAnnotationPresent(Table.class)){//查看有沒有Table 註解
return null;
}
Table table = (Table) c.getAnnotation(Table.class);
String tableName = table.value();
stringBuilder.append("select * from ").append(tableName).append(" where filter = 1");
//3 遍歷所有字段
Field[] fields = c.getDeclaredFields();
for (Field field :fields){
if(!field.isAnnotationPresent(Column.class)){
continue;
}
//4 獲取字段名字
Column column = field.getAnnotation(Column.class);
String columnName = column.value();
//5 獲取字段值 通過反射
String fieldName = field.getName();
String getMethodName = "get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
Object fieldValue = null;
try {
Method method = c.getMethod(getMethodName);
fieldValue = method.invoke(filter);
} catch( Exception e) {
e.printStackTrace();
}
//6 拼裝sql
if(fieldValue==null||((fieldValue instanceof Integer)&&(Integer)fieldValue==0)){
continue;
}
stringBuilder.append(" and ").append(fieldName).append("=");
if(fieldValue instanceof String){
stringBuilder.append("'"+fieldValue+"'");
}else if(fieldValue instanceof Integer){
stringBuilder.append(fieldValue);
}else{//其他情況
}
}
return stringBuilder.toString();
}
控制檯打印數據
03-24 17:44:09.223 1390-1390/? E/MainActivity: onCreate: select * from user where filter = 1 and id=10
03-24 17:44:09.225 1390-1390/? E/MainActivity: onCreate: select * from user where filter = 1 and userName='lucy'
03-24 17:44:09.226 1390-1390/? E/MainActivity: onCreate: select * from user where filter = 1 and email='[email protected]'