雖然在平時開發(fā)中經(jīng)常使用注解,卻不知道如何自定義一個注解類型以及注解的實現(xiàn)原理。抽時間學(xué)習(xí)了一下,記錄下來加深理解。
1. 注解是什么
之前看到一篇文章將注解理解為“標(biāo)簽”,感覺還是比較貼切的。我們可以把注解理解為給包、類、方法、字段打的一個標(biāo)簽,并利用java的反射機制對注解標(biāo)注的類、方法或者字段進行相應(yīng)的處理。
2. 定義注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String name() default "";
}
利用關(guān)鍵字@interface聲明一個注解類型,并利用@Retention、@Target、@Document、@Inherited等元注解對注解進行定義。
- @Retention
用于自定義注解類型的元注解。retention的意思是保留,@Retention用來定義自定義注解有效期。有三種取值:
public enum RetentionPolicy {
/**
* 注解只在源碼文件中保留,不會被編譯器編譯
*/
SOURCE,
/**
* 會被編譯到生成的class文件中,但不會在運行時保留
*/
CLASS,
/**
* 運行時有效,可以通過反射機制讀取到該注解定義
*/
RUNTIME
}
- @Target
用于定義注解的使用目標(biāo)。我們知道注解可以用在包、類、方法、字段等字面量上,但不能將類的注解用到方法上,JDK使用@Target定義注解的使用目標(biāo)。在java.lang.annotation.ElementType 枚舉類型中定義了若干注解目標(biāo)枚舉常量:TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE等。
ElementType.TYPE 用于定義類、接口、枚舉注解;
ElementType.METHOD 用于定義方法注解;
ElementType.FIELD 用于定義字段注解;
ElementType.CONSTRUCTOR 用于定義構(gòu)造器注解; - @Document
這個元注解比較簡單,用于將該注解包含在Javadoc中 - @Inherited
在定義注解時加上@Inherited表示該注解具有"繼承性",這里的繼承性是指如果父類使用了自定義注解,則子類也繼承了該自定義注解。舉例說明:
/**
* 自定義注解AnnotationDemo、使用@Inherited表明該注解注解具有繼承性
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String name() default "";
}
/**
* 使用自定義注解修飾的父類
*/
@AnnotationDemo(name = "小明")
public class Super {
public void hello(){
System.out.println("I am super!");
}
}
/**
* 子類繼承Super超類
* 由于Super被@AnnotationDemo注解修飾,Child子類自動被@AnnotationDemo修飾,
*
*/
public class Child extends Super {
public void say(){
System.out.println("I am child!");
}
}
3. 注解的屬性
在自定義注解時可以為注解定義若干屬性,如在上邊定義的@AnnotationDemo注解中定義了一個名為name的屬性,默認(rèn)值為""。注解類型只有屬性沒有方法,且注解屬性聲明為屬性名+(),屬性類型可以為基本類型、String、Class、Enum等類型。注解的屬性使用default關(guān)鍵字聲明該屬性的默認(rèn)值,如果不為屬性聲明默認(rèn)值,則必須在使用注解時為該屬性賦值。
特殊的:如果屬性名為value,在使用注解時可以不指定屬性名賦值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String value() default "";
}
/**
* @AnnotationDemo注解的屬性名為value,使用時可以不指定屬性名為屬性賦值
* 等價于@AnnotationDemo(value="super")
*/
@AnnotationDemo("super")
public class Super {
public void hello(){
System.out.println("hello world!");
}
}
如果注解定義了多個屬性,使用時不同的屬性賦值用逗號分隔。
4. 注解與反射
正如在本文開頭所說的注解相當(dāng)于給類、方法、字段打的標(biāo)簽,對于運行時有效的注解需要通過JDK提供的反射機制來讓標(biāo)簽起作用。下面通過一個簡單的例子來演示如何利用反射機制使注解起作用。
- 自定義一個注解@Run
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Run {
}
- 使用定義的注解標(biāo)注方法
public class RunTest {
@Run
public void test1(){
System.out.println("run test1");
}
public void test2(){
System.out.println("run test2");
}
}
- 通過反射API對@Run標(biāo)記的方法執(zhí)行調(diào)用
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
try {
Class clazz = Class.forName("com.annotation.demo.test.RunTest");
Method[] methods = clazz.getMethods();
RunTest junitTest =(RunTest) clazz.newInstance();
for (Method method : methods){
if (method.isAnnotationPresent(Run.class)){
method.setAccessible(true);
method.invoke(junitTest);
}
}
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (InstantiationException e){
e.printStackTrace();
}catch (IllegalAccessException e){
e.printStackTrace();
}catch (InvocationTargetException e){
e.printStackTrace();
}
}
}
在這個例子中首先定義了一個@Run注解,并在RunTest中定義了兩個方法。其中一個方法使用了@Run進行注解,另一個方法沒有添加@Run注解,利用反射API對使用了@Run注解的方法進行調(diào)用。輸出結(jié)果也正如我們所料:只有RunTest中的test1方法被執(zhí)行。
OK,java注解的介紹就到這里。
你的關(guān)注是我持續(xù)更新的動力!