一起來學Java註解(Annotation)
目錄
- 一. 什麼是Annotation
- 二. Annotation的作用
- 2.1 編譯器使用到的註解
- 2.2 .class檔案使用到的註解
- 2.3 執行期讀取的註解
- 三. 定義Annotation
- 3.1 元註解
- 3.2 定義註解小結
- 四. Annotation處理
- 五. 總結
一. 什麼是Annotation
我們在平時的開發過程中看到很多如@Override,@SuppressWarnings,@Test等樣式的程式碼就是註解,註解是放到類、構造器、方法、屬性、引數前的標記。
二. Annotation的作用
給某個類、方法..添加了一個註解,這個環節僅僅是做了一個標記,對程式碼本身並不會造成任何影響,需要後續環節的配合,需要其他方法對該註解賦予業務邏輯處理。就如同我們在微信上發了一個共享定位,此時並沒有什麼用,只有當後面其他人都進入了這個共享定位,大家之間的距離才能明確,才知道該怎麼聚在一起。
註解分為三類:
2.1 編譯器使用到的註解
如@Override,@SuppressWarnings都是編譯器使用到的註解,作用是告訴編譯器一些事情,而不會進入編譯後的.class檔案。
@Override:告訴編譯器檢查一下是否重寫了父類的方法;
@SuppressWarnings:告訴編譯器忽略該段程式碼產生的警告;
對於開發人員來說,都是直接使用,無需進行其他操作
2.2 .class檔案使用到的註解
需要通過工具對.class位元組碼檔案進行修改的一些註解,某些工具會在類載入的時候,動態修改用某註解標註的.class檔案,從而實現一些特殊的功能,一次性處理完成後,並不會存在於記憶體中,都是非常底層的工具庫、框架會使用,對於開發人員來說,一般不會涉及到。
2.3 執行期讀取的註解
一直存在於JVM中,在執行期間可以讀取的註解,也是最常用的註解,如Spring的@Controller,@Service,@Repository,@AutoWired,Mybatis的@Mapper,Junit的@Test等,這類註解很多都是工具框架自定義在執行期間發揮特殊作用的註解,一般開發人員也可以自定義這類註解。
三. 定義Annotation
我們使用@interface來定義一個註解
/**
* 定義一個Table註解
*/
public @interface Table {
String value() default "";
}
/**
* 定義一個Colum註解
*/
public @interface Colum {
String value() default "";
String name() default "";
String dictType() default "";
}
這樣就簡單地將一個註解定義好了
我們上面定義的註解主要用到了String型別,但實際上還可以是基本資料型別(不能為包裝類)、列舉型別。
註解也有一個約定俗成的東西,最常用的引數應該命名為value,同時一般情況下我們都會通過default引數設定一個預設值。
但這樣是不是就滿足於我們的使用了呢,我想把@Table
註解僅用於類上,@Colum
註解僅用於屬性上,怎麼辦?而且開始提到的三類註解,一般開發人員用的都是執行期的註解,那我們定義的是嗎?
要回答這些問題,就需要引入一個概念“元註解”。
3.1 元註解
可以修飾註解的註解即為元註解,Java已經定義了一些元註解,我們可以直接使用。
3.1.1 @Target
顧名思義指定註解使用的目標物件,引數為ElementType[]
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();
}
而下面是ElementType列舉中定義的屬性,不設定Target的時候,除了TYPE_PARAMETER,TYPE_USE,其他地方都相當於配置上了。
public enum ElementType {
/** 通過ElementType.TYPE可以修飾類、介面、列舉 */
TYPE,
/** 通過ElementType.FIELD可以修飾類屬性 */
FIELD,
/** 通過ElementType.METHOD可以修飾方法 */
METHOD,
/** 通過ElementType.PARAMETER可以修飾引數(如構造器或者方法中的) */
PARAMETER,
/** 通過ElementType.CONSTRUCTOR可以修改構造器 */
CONSTRUCTOR,
/** 通過ElementType.LOCAL_VARIABLE可以修飾方法內部的區域性變數 */
LOCAL_VARIABLE,
/** 通過ElementType.ANNOTATION_TYPE可以修飾註解 */
ANNOTATION_TYPE,
/** 通過ElementType.PACKAGE可以修飾包 */
PACKAGE,
/**
* 可以用在Type的宣告式前
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 可以用在所有使用Type的地方(如泛型、型別轉換等)
*
* @since 1.8
*/
TYPE_USE
}
我們主要說一下ElementType.PACKAGE和1.8新增的ElementType.TYPE_PARAMETER和ElementType.TYPE_USE
ElementType.PACKAGE
@Target(ElementType.PACKAGE)
public @interface Table {
String value() default "";
}
含義是用來修飾包,但我們用來修飾包的時候卻提示錯誤
我們按照提示建立package-info.java檔案,這裡需要注意一下,通過IDE 進行new --> Java Class是建立不了的,需要通過new File檔案建立
@Table
package annotation;
class PackageInfo {
public void hello() {
System.out.println("hello");
}
}
ElementType.TYPE_PARAMETER和ElementType.TYPE_USE
這兩個一起說,因為它們有相似之處。都是Java1.8後新增的
@Target(ElementType.TYPE_USE)
public @interface NoneEmpty {
String value() default "";
}
@Target(ElementType.TYPE_PARAMETER)
public @interface NoneBlank {
String value() default "";
}
很明顯使用ElementType.TYPE_PARMETER修飾的註解@NoneBlank無法在泛型使用的時候編譯通過,僅能用於類的泛型宣告,而通過ElementType.TYPE_USE修飾的註解@NoneEmpty可以。
3.1.2 @Retention
可以用於定義註解的生命週期,引數為列舉RetentionPolicy,包括了SOURCE,CLASS,RUNTIME
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
public enum RetentionPolicy {
/**
* 僅存在於原始碼中,編譯階段會被丟棄,不會包含於class位元組碼檔案中.
*/
SOURCE,
/**
* 【預設策略】,在class位元組碼檔案中存在,在類載入的時被丟棄,執行時無法獲取到
*/
CLASS,
/**
* 始終不會丟棄,可以使用反射獲得該註解的資訊。自定義的註解最常用的使用方式。
*/
RUNTIME
}
3.1.3 @Documented
表示是否將此註解的相關資訊新增到javadoc文件中
3.1.4 @Inherited
定義該註解和子類的關係,使用此註解宣告出來的自定義註解,在使用在類上面時,子類會自動繼承此註解,否則,子類不會繼承此註解。注意,使用@Inherited宣告出來的註解,只有在類上使用時才會有效,對方法,屬性等其他無效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Person {
String value() default "man";
}
@Person
public class Parent {
}
//子類也擁有@Person註解
class Son extends Parent {
}
3.2 定義註解小結
用@interface定義註解
可以新增多個引數,核心引數按約定用value,為每個引數可以設定預設值,引數型別包括基本型別、String和列舉
可以使用元註解來修飾註解,元註解包括多個,必須設定@Target
和@Retention
,@Retention
一般設定為RUNTIME
。
四. Annotation處理
我們前面已經提到光配置了註解,其實沒有作用,需要通過相應的程式碼來實現該註解想要表達的邏輯。
註解定義後也是一種class,所有的註解都繼承自java.lang.annotation.Annotation
,因此,讀取註解,需要使用反射API。
//定義的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Colum {
String value() default "";
//用於表示某個屬性代表的中文含義
String name() default "";
}
用註解@Colum來修飾某個類的屬性
public class Person {
@Colum(name = "姓名")
private String name;
@Colum(name = "性別")
private String gender;
@Colum(name = "年齡")
private int age;
@Colum(name = "住址")
private String address;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getGender() {return gender;}
public void setGender(String gender) {this.gender = gender;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public String getAddress() {return address;}
public void setAddress(String address) {this.address = address;}
}
通過反射讀取這個類的所有欄位的中文含義,並儲存到list中,然後打印出來
public static void main(String[] args) throws ClassNotFoundException {
List<String> columNames = new ArrayList<>();
Class clazz = Class.forName("annotation.Person");
//獲取Person類所有屬性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields){
//獲取該屬性的Colum註解
Colum colum = field.getAnnotation(Colum.class);
//或者可以先判斷有無該註解
field.isAnnotationPresent(Colum.class);
//將該屬性通過註解配置好的中文含義取出來放到集合中
columNames.add(colum.name());
}
//列印集合
columNames.forEach((columName) -> System.out.println(columName));
}
結果如下:
姓名
性別
年齡
住址
比如我們有一些常見的應用場景,需要把網站上的列表匯出成excel表格,我們通過註解的方式把列名配置好,再通過反射讀取實體需要匯出(是否需要匯出,也可通過註解配置)的每個欄位的值,從而實現excel匯出的元件。
五. 總結
本文只是拋磚引玉地講解了註解的基本概念,註解的作用,幾種元註解的功用以及使用方法,並通過一個簡單的例子講解了一下註解的處理,並不全面,文中通過Field講解了註解的基本Api,但註解還可以修飾類、構造器、方法等,也有相對應的註解處理方法,大家可自行查一下API手冊相關內容,大同小異,有不對之處,請批評指正,望共同進步,謝