自定義註解並且進行掃描解析

註解背景

首先我們要知道背景知識:

  1. 每一個註解其實就是一個特殊的接口(帶着@符號,其實是語法糖,會被編譯器自動編譯成繼承自Annotation接口)。我們反編譯一下class文件就能看出來。
  2. 註解只是一個標記位,標記了某一個類,某一個字段或者某一個函數之後,我們就可以對被標記的屬性進行我們期望的行爲——比如運行時動態獲取和修改被標記的屬性,動態執行被標記的函數等等
  3. 基於第二點,我們在定義了自己的註解之後,還要定義自己註解的解析類,這樣我們才能真正讓註解發揮起作用(只標記而不做任何動作就和沒標記沒任何區別了)

源碼解析

廢話不說上我自己定義的代碼,然後一個一個說明。

package com.springtest.demo.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    String name() default "";

    enum FruitType {
        APPLE, PEACH, PEAR, WATERMELON
    }

    FruitType type() default FruitType.APPLE;
}
package com.springtest.demo.entity.fruit;

import com.springtest.demo.annotation.Fruit;
import com.springtest.demo.annotation.Scope;

@Scope(Scope.SCOPE_PROTOTYPE)
public class Pear {
    @Fruit(value = "pear")
    private String name;

    @Fruit(type = Fruit.FruitType.PEAR)
    private String type;
}

package com.springtest.demo.entity.fruit;

import com.springtest.demo.annotation.Fruit;
import com.springtest.demo.annotation.Scope;

@Scope(Scope.SCOPE_PROTOTYPE)
public class Apple {
    @Fruit(value = "apple")
    private String name;

    @Fruit(type = Fruit.FruitType.APPLE)
    private String type;
}

以上就是我做的最簡單的demo,定義並應用了一個註解。我們來看看Fruit註解反編譯的結果是什麼就能大概知道這故事背後的作用。

public interface Fruit
    extends Annotation
{
    public static final class FruitType extends Enum
    {

        public static final FruitType APPLE;
        public static final FruitType PEACH;
        public static final FruitType PEAR;
        public static final FruitType WATERMELON;
        private static final FruitType $VALUES[];

        public static FruitType[] values()
        {
            return (FruitType[])$VALUES.clone();
        }

        public static FruitType valueOf(String name)
        {
            return (FruitType)Enum.valueOf(com/springtest/demo/annotation/Fruit$FruitType, name);
        }

        static 
        {
            APPLE = new FruitType("APPLE", 0);
            PEACH = new FruitType("PEACH", 1);
            PEAR = new FruitType("PEAR", 2);
            WATERMELON = new FruitType("WATERMELON", 3);
            $VALUES = (new FruitType[] {
                APPLE, PEACH, PEAR, WATERMELON
            });
        }

        private FruitType(String s, int i)
        {
            super(s, i);
        }
    }


    public abstract String value();

    public abstract String name();

    public abstract FruitType type();
}

查看代碼我們發現所謂註解的本質還是很簡單的其實就是一個繼承了Annotation的接口,然後內部定義了一些默認的抽象類而已。

Retention註解

由於因爲我們知道其實一個註解本質上就只是一個標記,這個標記要怎麼使用,什麼時候使用是我們的編譯器和jvm決定的,也就意味着一個註解通常會有一個目的,或者我們叫作用域。通常分爲三類:

  1. 僅在編碼時生效 @RetentionPolicy .SOURCE
  2. 僅在編譯時生效 @RetentionPolicy .CLASS
  3. 僅在運行時生效 @RetentionPolicy .RUNTIME
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

/**
 * Annotation retention policy.  The constants of this enumerated type
 * describe the various policies for retaining annotations.  They are used
 * in conjunction with the {@link Retention} meta-annotation type to specify
 * how long annotations are to be retained.
 *
 * @author  Joshua Bloch
 * @since 1.5
 */
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
}

Target 註解

同時由於我們的註解是可能被寫在各種地方的,因此我們需要定義我們這個參數的作用域。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
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();
}

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
     */
    TYPE_PARAMETER,

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

Documented註解

最後就是一個是否需要被Javadoc記錄的標記位@Documented。

小結

基於遇上三點,也就是爲什麼我們常見的註解,頭上都會有這三個標記的原因。因此,我們再手動實現另一個註解,可以再理解一下:

package com.springtest.demo.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
    String SCOPE_SINGLETON = "singleton";

    String SCOPE_PROTOTYPE = "prototype";

    // 默認是單例
    String value() default Scope.SCOPE_SINGLETON;
}

看看上面這個註解,是不是很眼熟?~~

沒錯,就是我們在Spring中常用的@Scope註解的照搬版~試試分析看這個註解是怎麼表達的。

接下來開始進行註解的解析,這裏因爲我們直接定義成運行時,所以可以在運行中通過類的反射機制,找到我們這個註解對應的作用域,如果被我們註解了,並且註解內的條件達到了,我們就對這個作用域內的對象進行某些我們想要的操作就行了。下面附上我的源代碼以及編譯器編譯後的class的反編譯結果。

package com.springtest.demo.annotation;

import com.springtest.demo.config.YunyaoBeanPostProcessor;

import javax.annotation.PostConstruct;
import java.lang.reflect.Field;

/**
 * 註解驅動
 */
@Scope(Scope.SCOPE_PROTOTYPE)
public class FruitInfoUtil {
    public static void getFruitInfo(Object obj) {
        String strFruitName = " 水果名稱:";

        Field[] fields = obj.getClass().getDeclaredFields();

        for (Field field : fields) {
            if (field.isAnnotationPresent(Fruit.class)) {
                Fruit fruit = field.getAnnotation(Fruit.class);
                strFruitName = strFruitName + fruit.value() + fruit.type().name();
                System.out.println(strFruitName);
            }
        }
    }

    // 注:同一個對象,被實力出多個不同value的單例時,PostConstruct只會被執行一次
    @PostConstruct
    public void postConstruct() {
        System.out.println("PostConstruct被執行..." + this.getClass().getName());
    }
}


    public static void getFruitInfo(Object obj)
    {
        String strFruitName = " \u6C34\u679C\u540D\u79F0\uFF1A";
        Field fields[] = obj.getClass().getDeclaredFields();
        Field afield[] = fields;
        int i = afield.length;
        for(int j = 0; j < i; j++)
        {
            Field field = afield[j];
            if(field.isAnnotationPresent(com/springtest/demo/annotation/Fruit))
            {
                Fruit fruit = (Fruit)field.getAnnotation(com/springtest/demo/annotation/Fruit);
                strFruitName = (new StringBuilder()).append(strFruitName).append(fruit.value()).append(fruit.type().name()).toString();
                System.out.println(strFruitName);
            }
        }
    }

    public void postConstruct()
    {
        System.out.println((new StringBuilder()).append("PostConstruct\u88AB\u6267\u884C...").append(getClass().getName()).toString());
    }

總結

其實說到最後,註解真的很容易,而且很簡單易用,只要我們搞清楚:

  1. 註解的本質是什麼 – 本質上註解就是一個JDK提供給我們的標記位,我們需要自己定義這個標記會會在什麼條件下觸發什麼動作
  2. 基於第一點,我們甚至可以自己定義執行函數,來對其他三方組件或者JDK提供的註解進行自定義補充解析。
  3. 基於第二點,我們就可以知道,註解的靈魂不在註解本身,而在於誰來解析這個註解(所謂一千個…)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章