Java Annotations

Java Annotations

Java 從JDK1.5引入了註解,用於對元數據(metadata)的支持。經過註解能夠對程序元素如類,成員方法,成員變量添加額外的補充信息。java

​ 經過註解咱們能夠對程序元素進行註釋說明,甚至改變其行爲,不過須要咱們對其進行相應的解析處理,不然它除了註釋之外不會起到任何實際性的做用。數組

​ jdk經過java.lang.annotation包提供對註解的支持,註解類型實際上是一種特殊的接口(interface)類型,因此不能同時定義相同名稱的類,接口或註解類型,爲了與接口類型區分,在接口interface前面加一個@符號,即@interface,全部的註解類型默認實現了java.lang.annotation.Annotation接口。安全

​ 此外,jdk預約義了一系列註解,按其做用能夠將它們元註解(用於註解其餘註解類型)與普通註解(用於註解其餘程序元素),經過元註解咱們還能夠建立自定義註解,這與系統定義的普通註解沒什麼區別。微信

一.元註解

這類註解專門做用於對其餘類型的註解,具備相同的Target@Target(ElementType.ANNOTATION_TYPE)修飾mybatis

  • @Documentedapp

    @since 1.5: 經過@Documented註解的註解類型,能夠經過javadoc工具提取其信息到修飾的程序元素API中。框架

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    複製代碼
  • @Targetide

    @since 1.5: 用於指定被修飾註解類型能夠做用的範圍。函數

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

    全部範圍定義在ElementType枚舉當中。工具

    類型 做用
    TYPE 類,接口(包括註解類型),或枚舉聲明的地方
    FIELD 變量聲明(包括枚舉常量)的地方
    METHOD 方法聲明的地方
    PARAMETER 形參聲明的地方
    CONSTRUCTOR 構造方法聲明的地方
    LOCAL_VARIABLE 本地變量聲明的地方
    ANNOTATION_TYPE 註解類型聲明的地方
    PACKAGE 包聲明的地方
    TYPE_PARAMETER 類型參數聲明的地方 @since 1.8
    TYPE_USE 類型使用的地方 @since 1.8
  • @Retention

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

    @since 1.5: 用於指定所修飾註解的保留範圍,具體由RetentionPolicy指定。

    public enum RetentionPolicy {
        /** 僅在原文件保留,編譯時被丟棄 */
        SOURCE,
      	/** 保留在生成的class文件當中,但不會被VM加載。注意:局部變量上的註解不會被編譯進class文件 */
        CLASS,
        /** 保留在生成的class文件當中,而且在VM運行時保留,能夠經過反射讀取 */
        RUNTIME
    }
    複製代碼
  • @Inherited

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

    @since 1.5: 用於指定所修飾註解能夠被它所註解的類的子類繼承。例如註解@A註解了類Base,則它的子類Sub class也會被@A註解。

  • @Repeatable

    默認是不能有多個相同的annotation同時註解同一程序元素的。在jdk1.8之前,咱們只能使用一個容器(元素是另外一個annotation類型的數組)來間接達到相同的目的,以下面代碼:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Author {
        String value();
    }
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Authors {
        Author[] value();
    }
    
    @Authors({@Author("Yannis Zhao"), @Author("Jack")})
    public class Demo {
    }
    複製代碼

    jdk1.8爲重複註解提供了支持,經過Repeatable註解,咱們能夠在程序元素上直接使用重複的註解,只需對上面代碼稍加調整便可:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    /** * 指定可重複註解的容器註解類型 */
    @Repeatable(Authors.class)
    public @interface Author {
        String value();
    }
    
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Authors {
        Author[] value();
    }
    
    /** * 如今就能夠直接使用重複註解了 */
    @Author("Yannis Zhao")
    @Author("Jack")
    public class Demo {
    }
    複製代碼

    **注意:**Authors的保留範圍要不能比Author的小,還有@Repeatable的value指定的annotation,其value返回必須是@Repeatable所註解的類型的數組

  • @Native

二.基本註解
  • @Override

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    }
    複製代碼

    標記註解(Marker Annotation),指示子類重寫或者了一個父類/接口的方法,編譯器會檢查父類,接口中有沒有這樣一個方法。

  • @Deprecated

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
    public @interface Deprecated {
    }
    
    複製代碼

    標記一個程序元素已通過時,並將在後期的版本中刪除。做者應該儘量註明將在哪一個版本中將其移除,並給出(若是有)替換方案。使用者不該該在新代碼中使用它,並且應該儘快修改以前用到它的部分。

  • @SuppressWarning

    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
        String[] value();
    }
    
    複製代碼

    指定編譯器取消其註解的元素及子元素上的編譯警告,如泛型檢查相關的警告

  • @Safevarargs

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
    public @interface SafeVarargs {}
    
    複製代碼

    參數安全類型註解,jdk1.7引入,在jdk1.7後,編譯器將會對泛型進行更嚴格的檢查,防止發生類型轉換異常。經過此註解來告訴編譯器本身的代碼是類型安全的,不要拋出類型檢查警告。

  • @FunctionalInterface

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface FunctionalInterface {}
    
    複製代碼

    函數式接口(接口中只有一個抽象方法)註解,jdk1.8引入,用於支持lambda表達式支持

三.自定義註解

​ 自定義註解與jdk中定義的如@Deprecated同樣,只須要用@interface聲明一個註解類型,並經過@Target指定其做用元素,@Retention指定其生存時間,@Document指定是否須要被javadoc提取便可。

​ 此外,註解還能夠包含成員,類型能夠是基本類型,String,Class,enum,Annotation,數組。而且每一個成員還能夠指定默認值。好比@Resource註解

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    String lookup() default "";
    Class<?> type() default java.lang.Object.class;
  
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
}

複製代碼
四.註解解析
  • 源代碼

    ​ 對應RetentionPolicy.SOURCE,jdk並無提供這種註解相應的解析方式,不過一些第三方工具提供了一些註解及其解析以幫助對代碼進行檢查。

  • 編譯時

    ​ 編譯時處理註解會掃描全部文件,包括新生成的文件,直到沒有新的要處理的文件。

    ​ 在jdk5中,經過一個APT(Annotation Process Tool)工具來在編譯時進行解析的,用com.sun.mirror.*下一系列類來描述代碼的靜態結構。用戶須要實現兩個接口AnnotationProcessorFactory(註解處理器工廠)和AnnotationProcessor(註解處理器)。這種方式不只實現起來繁瑣,並且它是Oracle的私有實現。因此在jdk6中經過 JSR 269對其進行了規範。提供了javax.annotation.processing(處理API)和javax.lang.model(Mirror API),並能夠經過javac處理。

    ​ 在jdk6中,只需實現javax.annotation.processing.Processor或者通常繼承抽象類javax.annotation.processing.AbstractProcessor便可。經過編譯時處理,能夠生成新的文件,還能夠修改類的AST添加任何代碼。

    關於編譯時解析我會在另外一篇文章中介紹

  • 運行時

    ​ 對於RetentionPolicy.RUNTIME的註解,編譯器會將其編譯進class文件的RuntimeVisibleAnnotations屬性表中,因此能夠經過Reflect API在運行時讀取。運行時註解解析普遍應用在如Spring,mybatis等許多第三方框架中。

    ​ 這種方式主要涉及到一個接口AnnotatedElement, Class, Constructor,Method,Parameter等元素都實現了此接口,因此能夠直接經過這些類調用AnnotatedElement接口中定義的相關方法獲取其上的註解信息。

    public interface AnnotatedElement {
        /** * 判斷元素是否被annotationClass註解 */
        default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
            return getAnnotation(annotationClass) != null;
        }
    
       /** * 返回該元素上指定類型的註解對象 */
        <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    
        /** * 返回該元素上的全部註解 */
        Annotation[] getAnnotations();
    
        /** * 1.8新增,獲取指定類型的註解,而且會處理可重複註解 */
        default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
             ...
         }
    
        /** * 1.8新增,返回且只返回直接註解在該元素上的註解,沒有返回null */
        default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
             ...
         }
    
        /** * 1.8新增,返回且只返回直接註解在該元素上的註解,而且會處理可重複註解,沒有返回空數組 */
        default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
            ...
        }
    
        /** * 返回全部直接註解在該元素上的註解 */
        Annotation[] getDeclaredAnnotations();
    }
    
    複製代碼

    舉個簡單🌰,經過一個註解來實現後臺操做日誌的統一紀錄:

    定義日誌註解:

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AdminLog {
    
        LogType type();
        String desc() default "";
    
        enum LogType {
            ADD(1), EDIT(2), DELETE(3);
    
            private int type;
    
            LogType(int type) {
                this.type = type;
            }
        }
    }
    
    複製代碼

    在Controller方法上聲明註解:

    @RequestMapping("addUser")
    @AdminLog(type = LogType.ADD, desc = "Add User")
    public String addUser(String name, Integer age) {
        return name + ":" + age;
    }
    
    複製代碼

    在Spring攔截器中處理註解:

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    
        if (handler instanceof HandlerMethod) {
    
            HandlerMethod hm = (HandlerMethod) handler;
            Method method = hm.getMethod();
            AdminLog annotation = method.getAnnotation(AdminLog.class);
            if (annotation != null) {
                // write db
                System.out.println(annotation.type());
                System.out.println(annotation.desc());
                Map<String, String[]> parameterMap = request.getParameterMap();
                System.out.println(JSON.toJSONString(parameterMap));
                System.out.println(response.getStatus());
            }
    
        }
    }
    
    複製代碼

全部文章微信公衆號第一時間更新

相關文章
相關標籤/搜索