雖然在平時(shí)開發(fā)中經(jīng)常使用注解煌往,卻不知道如何自定義一個(gè)注解類型以及注解的實(shí)現(xiàn)原理妙真。抽時(shí)間學(xué)習(xí)了一下景东,記錄下來加深理解。
1. 注解是什么
之前看到一篇文章將注解理解為“標(biāo)簽”,感覺還是比較貼切的摄职。我們可以把注解理解為給包吊档、類簇爆、方法镀琉、字段打的一個(gè)標(biāo)簽,并利用java的反射機(jī)制對(duì)注解標(biāo)注的類翘地、方法或者字段進(jìn)行相應(yīng)的處理申尤。
2. 定義注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String name() default "";
}
利用關(guān)鍵字@interface聲明一個(gè)注解類型癌幕,并利用@Retention、@Target昧穿、@Document勺远、@Inherited等元注解對(duì)注解進(jìn)行定義。
- @Retention
用于自定義注解類型的元注解时鸵。retention的意思是保留胶逢,@Retention用來定義自定義注解有效期。有三種取值:
public enum RetentionPolicy {
/**
* 注解只在源碼文件中保留饰潜,不會(huì)被編譯器編譯
*/
SOURCE,
/**
* 會(huì)被編譯到生成的class文件中初坠,但不會(huì)在運(yùn)行時(shí)保留
*/
CLASS,
/**
* 運(yùn)行時(shí)有效,可以通過反射機(jī)制讀取到該注解定義
*/
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
這個(gè)元注解比較簡單,用于將該注解包含在Javadoc中 - @Inherited
在定義注解時(shí)加上@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子類自動(dòng)被@AnnotationDemo修飾,
*
*/
public class Child extends Super {
public void say(){
System.out.println("I am child!");
}
}
3. 注解的屬性
在自定義注解時(shí)可以為注解定義若干屬性悍手,如在上邊定義的@AnnotationDemo注解中定義了一個(gè)名為name的屬性帘睦,默認(rèn)值為""袍患。注解類型只有屬性沒有方法,且注解屬性聲明為屬性名+()竣付,屬性類型可以為基本類型诡延、String、Class古胆、Enum等類型肆良。注解的屬性使用default關(guān)鍵字聲明該屬性的默認(rèn)值,如果不為屬性聲明默認(rèn)值逸绎,則必須在使用注解時(shí)為該屬性賦值惹恃。
特殊的:如果屬性名為value棺牧,在使用注解時(shí)可以不指定屬性名賦值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String value() default "";
}
/**
* @AnnotationDemo注解的屬性名為value颊乘,使用時(shí)可以不指定屬性名為屬性賦值
* 等價(jià)于@AnnotationDemo(value="super")
*/
@AnnotationDemo("super")
public class Super {
public void hello(){
System.out.println("hello world!");
}
}
如果注解定義了多個(gè)屬性,使用時(shí)不同的屬性賦值用逗號(hào)分隔亥鸠。
4. 注解與反射
正如在本文開頭所說的注解相當(dāng)于給類、方法家妆、字段打的標(biāo)簽伤极,對(duì)于運(yùn)行時(shí)有效的注解需要通過JDK提供的反射機(jī)制來讓標(biāo)簽起作用哨坪。下面通過一個(gè)簡單的例子來演示如何利用反射機(jī)制使注解起作用徒溪。
- 自定義一個(gè)注解@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對(duì)@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();
}
}
}
在這個(gè)例子中首先定義了一個(gè)@Run注解,并在RunTest中定義了兩個(gè)方法礁凡。其中一個(gè)方法使用了@Run進(jìn)行注解,另一個(gè)方法沒有添加@Run注解,利用反射API對(duì)使用了@Run注解的方法進(jìn)行調(diào)用。輸出結(jié)果也正如我們所料:只有RunTest中的test1方法被執(zhí)行。
OK匈挖,java注解的介紹就到這里。
你的關(guān)注是我持續(xù)更新的動(dòng)力!