注解-你可能需要知道這些

在日常的開發工作中,尤其是在使用一些比較受歡迎的開源框架時,我們不可避免的都使用到了注解(Annotation),注解的使用范圍也越來越廣,而且在使用了注解后,我們的代碼看起來也更簡潔了。
本博文就帶大家來分析和學習下注解,并簡單分析下注解的在各種框架中的實現。

1. 什么是注解?

Java Annotation是JDK 5.0引入的一種解釋機制。
我們日常開發中最常見的注解恐怕就是@Override的了,它代表我們的方法是重寫的。
我們來看下@Override是如何定義的:

//使用在方法上
@Target(ElementType.METHOD)
// 該注解只在.java文件中存在,編譯后的.class文件中是不存在的
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解的定義使用關鍵字:@interface,有點像我們定義接口。

在定義Override注解時,又使用了一些其他的注解(@Target@Retention),這些注解被稱為元注解,是自定義注解的基礎和關鍵點。

2. 元注解

元注解是用來定義其他注解的注解,是不是覺得很費解,不要著急,我們來大家來認識下我們常用的元注解。

2.1 @Retention-Annotation能活多久

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}


public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

@Retention表示Annotation作用范圍和保留時間,如果未標注則默認使用CLASS。取值范圍如下:

  • SOURCE:只存在于源文件(.java)中,編譯器不會理會它。
  • CLASS:只存在于編譯后的文件(.class)中,編譯器會負責記錄它。(默認值)
  • RUNTIME:不僅存在于編譯后的文件(.class)中,而且還存在于JVM中,所以我們可以在運行時讀取和使用。

2.2 @Target-Annotation能勾搭誰

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     * @hide 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     * @hide 1.8
     */
    TYPE_USE
}

@Target表示Annotation可用用來修飾那些元素,如果未標注則表示可以修飾所有元素。取值范圍如下:

  • TYPE:修飾類、接口、枚舉
  • FIELD:修飾屬性
  • METHOD:修飾方法
  • PARAMETER:修飾方法參數
  • CONSTRUCTOR:修飾構造方法
  • LOCAL_VARIABLE:修飾局部變量
  • ANNOTATION_TYPE:修飾注解
  • PACKAGE:修飾包
  • TYPE_PARAMETER:泛型參數(1.8加入)
  • TYPE_USE:使用類型的地方(1.8加入)

2.3 @Inherited-被繼承

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Inherited標識某個被標注的類型是被繼承的。使用了@Inherited修飾的annotation類型被用于一個class之時,則這個annotation將被用于該class的相應子類。

假設,我們定義了注解MyAnnotation,MyAnnotation被標注為@Inherited。類Base使用了MyAnnotation,則Base具有了“具有了注解MyAnnotation”Sub類繼承了Base,由于MyAnnotation是@Inherited的(具有繼承性),所以Sub也“具有了注解MyAnnotation”。

2.4 @Documented-JavaDoc

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Documented所修飾的Annotation連同自定義Annotation所修飾的元素一同保存到Javadoc文檔中。

3.自定義注解

學習完了常用的元注解之后,我們便可以嘗試自定義注解了。

3.1 自定義注解

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MethodInfo {

    String author() default "ZSSir";

    String date();

    float version() default 1.0f;

}

我們來分析下如何自定義注解:

  1. 使用@interface來標識這是一個注解,后面跟的是注解名。
  2. 使用元注解來定義注解的相關信息。
  3. 定義方法(方法名其實是屬性名):
    • 所有的方法都沒有方法體(好像我們定義接口一樣)。
    • 方法不能拋出異常。
    • 方法不能使用除public和abstract的其他修飾符。
    • 方法返回只能為基本類型、String, Class, annotation, enumeration 或者是他們的一維數組。返回值類型即為屬性的類型。
    • 若只有一個屬性,可以直接使用value()方法;若不包含任何屬性,則表示該注釋為Mark Annotation。
    • 可以加 default 表示默認值,null不能作為成員默認值。

3.2 使用注解

public class AppInfo {
    @MethodInfo(author = "ZS",date = "2017/9/28",version = 1.0f)
    public static String getAppName(){
        return "Smart";
    }
}

4. 注解解釋

上個章節我們分析了如何自定義和使用注解,但是這個時候注解還沒有意義,我們必須要解釋注解,才能正確使用其功能。本章節就帶大家來分析下,如何來解釋我們的注解。

解釋注解最常用的有兩種方式:

  1. 運行時注解

  2. 編譯時注解

4.1 運行時注解

相信大家一定多多少少都了解一點關于反射的知識,反射、反射,程序員的快樂,可見反射在程序員心目中的地位是多么的高,而運行時注解其實就是使用反射技術來解釋注解的。

public class AppInfo {

    @MethodInfo(author = "ZS", date = "2017/9/28", version = 1.0f)
    public static String getAppName() {
        return "Smart";
    }

    public static void main(String[] args) {
        // 獲取類聲明的所有方法
        Method[] methods = AppInfo.class.getDeclaredMethods();
        for (Method method : methods) {
            //判斷方法是否被MethodInfo注解修飾
            boolean isPresent = method.isAnnotationPresent(MethodInfo.class);
            if (isPresent) {
                //獲取注解信息
                MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);
                System.out.println("method name:" + method.getName());
                System.out.println("method author:" + methodInfo.author());
                System.out.println("method version:" + methodInfo.version());
                System.out.println("method date:" + methodInfo.date());
            }

        }

    }

}

運行結果:
method name:getAppName
method author:ZS
method version:1.0
method date:2017/9/28

可見我們通過反射取得了Method,并獲取到了方法上的注解,這就是反射解釋注解的簡單示例。

下面我們在再來看一個使用在屬性上的注解:

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface FieldInfo {
    String value() default "";
}

使用注解FieldInfo:

public class AppInfo {

    @FieldInfo("core.xz.zs")
    private String mAppPackage = "com.zs.core";


    public static void main(String[] args) {
        AppInfo appInfo = new AppInfo();
        Field[] fields = appInfo.getClass().getFields();
        for (Field field : fields) {
            boolean isPresent = field.isAnnotationPresent(FieldInfo.class);
            if (isPresent) {
                FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class);

                //反射設置屬性值
                try {
                    field.setAccessible(true);
                    field.set(appInfo, fieldInfo.value());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }

        System.out.println(appInfo.mAppPackage);

    }
}

運行結果:

core.xz.zs

我們通過反射獲取到了注解,并將注解的屬性值成功設置給了appInfo的mAppPackage屬性。
如果你對SpringMvc比較了解,你會發現DI和IOC也使用了注解和反射技術。

4.2 編譯時注解

編譯時注解相對于運行時注解復雜,本章節只簡單的分析下如何實現,不做詳細描述。

編譯時Annotation指@Retention為CLASS 的Annotation,由apt(Annotation Processing Tool) 解析自動解析。
 a) 自定義類集成自AbstractProcessor
 b) 重寫其中的 process()函數,實際是 apt(Annotation Processing Tool) 在編譯時自動查找所有繼承自 AbstractProcessor 的類,然后調用他們的 process 方法去處理。
 
詳細的編譯時注解,大家可以自己分析學習下,在此不做描述。

5. 結束語

注解在我們日常開發中經常使用到,希望大家通過本博文能了解和學習到關于注解的知識,謝謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容