ThinkingInJava-自定義註解annotation

1.jdk提供的元註解

註解本身並沒有什麼作用,只有結合能解析該註解的類纔有作用。

1.1元註解簡介

元註解的作用就是負責註解其他註解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型作說明。Java5.0定義的元註解:
    1.@Target(表示註解放在哪個位置 類,屬性,還是方法)
    2.@Retention(表示該註解保留的階段)
    3.@Documented(標記註解)
    4.@Inherited(如果在類上使用,則表示該類的子類也可以使用該註解)
  這些類型和它們所支持的類在java.lang.annotation包中可以找到。
###1.2 元註解詳細
@Retention(RetentionPolicy .XXX)
一般一個類可以分爲 源碼,字節碼,運行(被jvm加載)這幾個階段。而要想註解能隨着類在不同的階段存在的話就得指明階段。每個階段都有一個值,而這些值通過一個enum來封裝了。

public enum RetentionPolicy {
    SOURCE, //表示在源碼階段保留
    CLASS, //表示在源碼,和編譯的字節碼 保留
    RUNTIME// 表示在源碼,和編譯的字節碼,和被jvm加載 保留

@Target(ElementType.XXX)
這個元註解表示 定義的註解可以放在什麼位置。一個類可能有不同的地方都需要註解 所以可以用ElementType.XXX的數組:{ElementType.XXX,ElementType.XXX}來表示。
該元註解的取值也是一個enum:

public enum ElementType {
    TYPE, //用於描述類、接口(包括註解類型) 或enum聲明 如表示該類是一個表的註解 @Table
    FIELD,//用於描述域
    METHOD,//用於描述方法
    PARAMETER,//用於描述參數
    CONSTRUCTOR,//用於描述構造器
    LOCAL_VARIABLE,//用於描述局部變量
    ANNOTATION_TYPE,//註解類型
    PACKAGE//用於描述包
}

@Documented
用於描述其它類型的annotation應該被作爲被標註的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。

@Inherited
@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。
注意:@Inherited annotation類型是被標註過的class的子類所繼承。類並不從它所實現的接口繼承annotation,方法並不從它所重載的方法繼承annotation。
當@Inherited annotation類型標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。

##2. 自定義註解

定義註解格式:
  public @interface 註解名 {定義體}
  
註解參數的可支持數據類型:
1.所有基本數據類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上所有類型的數組

Annotation類型裏面的參數該怎麼設定:
第一,只能用public或默認(default)這兩個訪問權修飾.例如,String value();這裏把方法設爲defaul默認類型;   
第二,參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.例如,String value();這裏的參數成員就爲String;  
第三,如果只有一個參數成員,最好把參數名稱設爲"value",後加小括號.例:下面的例子FruitName註解就只有一個參數成員。

在實現權限模塊的時候,我們可能回定義一個@Permission權限註解:


/**
 * Created by Worldly on 2017/10/16.
 */
@Retention(RetentionPolicy.RUNTIME) //表示該註解在三個階段都得保留
@Target(ElementType.METHOD) // 表示該註解用在方法上
public @interface Permission {
    //表示模塊值
    String module() default "";

    //表示權限值
    String privilege() default "";
}


/**
 * 刪除用戶的時候需要權限。
 * 表示某個用戶調用刪除操作時,加入@Permission表示該方法需要操作權限
 */
@Permission(module="user",privilege="delete")
public void deleteUser{}

##3. 怎麼使用註解

有很多系統中的數據是非常重要的,比如一個商品的price 和庫存數。但又經常需要變化,有變化就指不定什麼時候會出現問題,這時boss要追究責任人的時候到了。那我們系統如果能夠記錄 一條這個的 “單價從【10】變更爲【0.1】,庫存從【10】變更爲【1000】”的記錄 並且記錄操作人,操作時間。放到數據庫表中就非常完美了,說不定boss會嘉獎你呦,這怎麼實現呢?

  1. 首先我們要確定一下有那些重要的屬性是需要記錄變更記錄的,把他們封裝到一個model中如OperationLog
/**
 1. @Description 操作記錄實體
 2. @Author xiaoqx <[email protected]>
 3. @Version V1.0.0
 4. @Since 1.0
 5. @Date 2018/1/27
 */
public class OperationLog {
    //價格
    @FiledDescription("單價")
    private BigDecimal price;

    //總庫存
    @FiledDescription("總庫存數")
    private Integer totalCount;

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public Integer getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(Integer totalCount) {
        this.totalCount = totalCount;
    }

    @Override
    public String toString() {
        return "{\"OperationLog\":{"
                + "\"price\":\"" + price + "\""
                + ", \"totalCount\":\"" + totalCount + "\""
                + "}}";
    }
}

2.再寫一個字段註解用中文來描述該字段是什麼意思**@FiledDescription**

/**
 * @Description  字段註解
 * @Author xiaoqx <[email protected]>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2018/1/27
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FiledDescription {
    String value() default "";
}

  1. 在上文說過每個註解都必須有一個對應得解析類,不然這個註解就是沒有用的註解 我們可以定義一個註解處理器 FiledDescriptionHandler
/**
 * @Description 字段註解處理器
 * @Author xiaoqx <[email protected]>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2018/1/27
 */
public class FiledDescriptionHandler {

    public static String recordOperationLog(OperationLog oldLog,OperationLog newLog) throws Exception{
        StringBuilder sb = new StringBuilder();
        //獲取要記錄的字段
        Field[] declaredFields = OperationLog.class.getDeclaredFields();
        for(Field field:declaredFields){
            //獲取該字段上的註解
            FiledDescription annotation = field.getAnnotation(FiledDescription.class);
            //獲取該字段的get方法
            String method ="get"+field.getName().substring(0,1).toUpperCase()+field.getName().substring(1);
            //利用反射機制來獲取該字段的舊值
             Object oldValue = (Object) OperationLog.class.getMethod(method).invoke(oldLog);
             String oldValueStr="";
             if(oldValue!=null)
                 oldValueStr = oldValue.toString();
            //利用反射機制來獲取該字段的新值
            String newValueStr="";
             Object newValue = (Object) OperationLog.class.getMethod(method).invoke(newLog);
             if(newValue!=null)
                 newValueStr = newValue.toString();

            //記錄字段值變更的記錄
            if(!oldValueStr.equals(newValueStr))
                sb.append(annotation.value()+"從【"+oldValue+"】變更爲【"+newValue+"】,");
        }
        return sb.toString().substring(0,sb.toString().length()-1);
    }

}

4.我們可以寫個簡單的測試一下

/**
 * @Description
 * @Author xiaoqx <[email protected]>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2018/1/27
 */
public class Test {

    public static void main(String[]args) throws Exception{
        OperationLog operationLog = new OperationLog();
        operationLog.setPrice(BigDecimal.valueOf(10000));
        operationLog.setTotalCount(200);

        OperationLog operationLog1 = new OperationLog();
        operationLog1.setPrice(BigDecimal.valueOf(1));
        operationLog1.setTotalCount(2000);

        System.out.println(FiledDescriptionHandler.recordOperationLog(operationLog,operationLog1));
    }
}

測試結果爲
這裏寫圖片描述

由此我們成功的實現了一個可以記錄每個字段變更過程的日誌記錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章