自定義注解的理解及其應用


   在項目中經常會用到自定義注解,下面講解一下自定義注解的理解及其應用。

一、元注解

  元注解的作用就是負責注解其他注解。Java5.0定義了4個標准的meta-annotation類型,它們被用來提供對其它 annotation類型作說明。

  Java5.0定義的元注解:

  • @Target
  • @Retention
  • @Documented
  • @Inherited

@Target

   作用:描述該注解修飾的范圍,可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。

   取值(ElementType):

       CONSTRUCTOR:用於描述構造器;

       FIELD:用於描述域;

       LOCAL_VARIABLE:用於描述局部變量;

       METHOD:用於描述方法;

       PACKAGE:用於描述包;

       PARAMETER:用於描述參數;

      TYPE:用於描述類、接口(包括注解類型) 或enum聲明;

@Retention

   作用:描述該注解的生命周期,表示在什么編譯級別上保存該注解的信息。Annotation被保留的時間有長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)

  取值(RetentionPoicy):

      SOURCE:在源文件中有效(即源文件保留)

      CLASS:在class文件中有效(即class保留)     

      RUNTIME:在運行時有效(即運行時保留)

@Documented

    @Documented Annotation的作用是在生成javadoc文檔的時候將該Annotation也寫入到文檔中

@Inherited

  作用:@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。

二、自定義注解

  使用@interface自定義注解,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義注解時,不能繼承其他的注解或接口。@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。

定義注解格式:public @interface 注解名 {定義體}

注解參數的可支持數據類型:

  • 所有基本數據類型(int,float,boolean,byte,double,char,long,short)
  • String類型
  • Class類型
  • enum類型
  • Annotation類型
  • 以上所有類型的數組

參數定義要點

  •  只能用public或默認(default)這兩個訪問權修飾;
  • 參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組;
  • 如果只有一個參數成員,建議參數名稱設為value();
  • 注解元素必須有確定的值,要么在定義注解的默認值中指定,要么在使用注解時指定,非基本類型的注解元素的值不可為null。因此, 使用空字符串或負數作為默認值是一種常用的做法。

簡單的自定義注解實例:

/**
 *自定義注解MyAnnotation
 */
@Target(ElementType.TYPE) //目標對象是類型
@Retention(RetentionPolicy.RUNTIME) //保存至運行時
@Documented //生成javadoc文檔時,該注解內容一起生成文檔
@Inherited //該注解被子類繼承
public @interface MyAnnotation {
    public String value() default ""; //當只有一個元素時,建議元素名定義為value(),這樣使用時賦值可以省略"value="
    String name() default "devin"; //String
    int age() default 18; //int
    boolean isStudent() default true; //boolean
    String[] alias(); //數組
    enum Color {GREEN, BLUE, RED,} //枚舉類型
    Color favoriteColor() default Color.GREEN; //枚舉值
}


@MyAnnotation(
        value = "info",
        name = "myname",
        age = 99,
        isStudent = false,
        alias = {"name1", "name2"},
        favoriteColor = MyAnnotation.Color.RED
)
public class MyClass {
    //使用MyAnnotation注解,該類生成的javadoc文檔包含注解信息如下:
    /*
    @MyAnnotation(value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1","name2"}, favoriteColor = Color.RED)
    public class MyClass
    extends Object
     */
}


public class MySubClass extends MyClass{
    //子類MySubClass繼承了父類MyClass的注解
}

 解析注解信息

   Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。相應地,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素。

   實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義為運行時的Annotation后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。

   AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的如下七個方法來訪問Annotation信息:

  1. <T extends Annotation> T getAnnotation(Class<T> annotationClass) :返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null;
  2. Annotation[] getDeclaredAnnotation(Class<T>):返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null;與此接口中的其他方法不同,該方法將忽略繼承的注解;
  3. Annotation[] getAnnotations():返回該程序元素上存在的所有注解;
  4. Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注解;
  5. Annotation[] getAnnotationsByType(Class<T>):返回直接存在於此元素上指定注解類型的所有注解;
  6. Annotation[] getDeclaredAnnotationsByType(Class<T>):返回直接存在於此元素上指定注解類型的所有注解。與此接口中的其他方法不同,該方法將忽略繼承的注解;
  7. boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false;
/***********注解聲明***************/
/**
 * 水果名稱注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default " ";
}


/**
 * 水果顏色注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 顏色枚舉
     */
    public enum Color{BLUE, RED, GREEN};

    /**
     * 顏色屬性
     * @return
     */
    Color fruitColor() default Color.GREEN;
}


/**
 * 水果供應商注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應商編號
     * @return
     */
    public int id() default -1;

    /**
     * 供應商名稱
     * @return
     */
    public String name() default " ";

    /**
     * 供應商地址
     * @return
     */
    public String address() default " ";
}
/***********注解使用***************/
public class Apple {
    @FruitName("Apple")
    private String appleName;
    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;
    @FruitProvider(id = 1, name = "陝西紅富士集團", address = "陝西紅富士大廈")
    private String appleProvider;

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleName() {
        return appleName;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public void displayName(){
        System.out.println(getAppleName());
    }
}

/***********注解信息獲取***************/
public class AnnotationParser {
    public static void main(String[] args) {
        Field[] fields = Apple.class.getDeclaredFields();
        for (Field field : fields) {
            //System.out.println(field.getName().toString());
            if (field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = field.getAnnotation(FruitName.class);
                System.out.println("水果的名稱:" + fruitName.value());
            }else if (field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                System.out.println("水果的顏色:"+fruitColor.fruitColor());
            }else if (field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                System.out.println("水果供應商編號:" + fruitProvider.id() + " 名稱:" + fruitProvider.name() + " 地址:" + fruitProvider.address());
            }
        }
    }
}

/***********輸出結果***************/
水果的名稱:Apple
水果的顏色:RED
水果供應商編號:1 名稱:陝西紅富士集團 地址:陝西紅富士大廈

JDK8注解新特性

   JDK 8 主要有兩點改進:類型注解和重復注解

  • 類型注解:類型注解在@Target中增加了兩個ElementType參數:

               1、ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中;

               2、ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中(例如聲明語句、泛型和強制轉換語句中的類型);

從而擴展了注解使用的范圍,可以使用在創建類實例、類型映射、implements語句、throw exception聲明中的類型前面。例如:

1、創建類實例: new @Interned MyObject();

2、類型映射:myString = (@NonNull String) str;

3、implements 語句中 :class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }

4、throw exception聲明:void monitorTemperature() throws @Critical TemperatureException { ... }

簡單示例:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface Encrypted {
}


public class MyTypeAnnotation {
    @Encrypted String data;
    List<@Encrypted String> strings;
}

 類型注解的作用:

   首先,局域變量聲明中的類型注解也可以保留在類文件中,完整泛型被保留,並且在運行期可以訪問,從而有助於我們獲取更多的代碼信息;其次,類型注解可以支持在的程序中做強類型檢查。配合第三方工具check framework,可以在編譯的時候檢測出runtime error,以提高代碼質量;最后,代碼中包含的注解清楚表明了編寫者的意圖,使代碼更具有表達意義,有助於閱讀者理解程序,畢竟代碼才是“最根本”的文檔、“最基本”的注釋。

重復注解

  重復注釋就是運行在同一元素上多次使用同一注解,使用@Repeatable注解。之前也有重復使用注解的解決方案,但可讀性不是很好,例如:

public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseOldVersion {    
    @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
    public void doSomeThing(){
    }
}

 而現在的實現如下:

@Repeatable(Authorities.class)
public @interface Authority {
     String role();
}

public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseNewVersion {
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}

  不同的地方是,創建重復注解Authority時,加上@Repeatable,指向存儲注解Authorities,在使用時候,直接可以重復使用Authority注解。從上面例子看出,java 8里面做法更適合常規的思維,可讀性強一點。

參見:http://www.jianshu.com/p/4068da3c8d3d

 三、自定義注解的應用

(1)利用自定義注解打印接口調用時長日志

#創建ServiceLog類用來自定義注解
@Retention(RetentionPolicy.Runtime)
@Target(ElementType.METHOD)
public @interface ServiceLog {

}

#定義ServiceLogAspect類用來定義日志打印信息

@Component
@Aspect 
public class ServiceLogAspect {

   public ThreadLocal<Long> local = new ThreadLocal<Long>();
   
   @Pointcut("@annotation(com.test.XXX.ServiceLong)")
   public void pointCut() {
    
   } 

   @Before("pointCut()")
   public void before(JoinPoint point) {
    String methodName = point.getTarget().getClass().getName()+"."+point.getSignature().getName();
    local.set(System.currentTimeMillis());
   }

  @After("pointCut()")
   public void after(JoinPoint point) {
    long start = local.get();
    String methodName = point.getTarget().getClass().getName()+"."+point.getSignature().getName();
    System.out.println(System.currentTimeMillis()-start));
    }
   
  @AfterThrowing(pointcut="pointCut()",throwing="error")
   public void throwing(JoinPoint point,Throwable error) {
    System.out.println("error");
    }

}

 完成上述定義,如果需要記錄方法調用時長時,可以直接使用@ServiceLog注解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM