自定義註解

自定義註解

1.先看一個dome

import java.lang.annotation.*;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAutoRecord {
    // 強制記錄方法功能(使用註解必須填寫該屬性)
    String methodDesc();
	// 帶有默認值,非必填
    String defValue() default "";
}

對於上述dome中,有以下幾個屬性。

  • 註解定義用@interface關鍵字修飾

  • @Target註解,是專門用來限定某個自定義註解能夠被應用在哪些Java元素上面的

  • @Retention註解,翻譯為持久力、保持力。即用來修飾自定義註解的生命周期

  • @Documented註解,是被用來指定自定義註解是否能隨著被定義的java文件生成到JavaDoc文檔當中

  • @Inherited註解,是指定某個自定義註解如果寫在了父類的聲明部分,那麼子類的聲明部分也能自動擁有該註解

2.屬性解釋

  • @interface關鍵字修飾,表明這是一個註解,可將註解掛到別的頭上,至於掛到哪要看@Target註解了。

  • @Target註解,是專門用來限定某個自定義註解能夠被應用在哪些Java元素上面的

    @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();
    }
    

    從源碼中看出 該註解傳入 ElementType[] ,是一個數組,可以傳多個,如:

    @Target({ElementType.METHOD,ElementType.TYPE})
    

    上面是修飾的註解表明該註解可以用在方法上,也可以用在類上。具體屬性看源碼

    	/** 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
         * 類型參數聲明,1.8以後才有
         */
        TYPE_PARAMETER,
    
        /**
         * Use of a type
         *
         * @since 1.8 字體的使用
         */
        TYPE_USE
    
  • @Retention註解,可以理解為生效時期

    Retention 源碼

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        /**
         * Returns the retention policy.
         * @return the retention policy
         */
        RetentionPolicy value();
    }
    

    Retention 源碼中屬性值為RetentionPolicy對象,所以不能像Target一樣配置多個,下面看一下RetentionPolicy屬性的源碼:

    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         * 編譯器將丟棄注釋。
         * 這個屬性的表明只會將註解資訊保存到程式源碼中,在經過編譯器編譯後就會將資訊拋棄,不會保存到.class文件中,也不會被載入到Jvm中。基本就是看著玩呢,標記一下
         */
        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文件中,但不會載入到jvm中。標記一下,讀取class時候可以讀取到。
         */
        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
         * 這個屬性的表明只會將註解資訊保存到程式源碼中,也會保存到.class文件中,也會載入到jvm中。在程式中可以讀取到哪些類或方法標記了該註解,便於我們後續操作
         */
        RUNTIME
    }
    

    測試這三個屬性,創建三個註解,Retention分別標記為RUNTIME,SOURCE,CLASS

    @Retention(RetentionPolicy.RUNTIME)
    public @interface LogAutoRecord { // 省略 1
        
    @Retention(RetentionPolicy.CLASS)
    public @interface LogAutoRecordTestClass { // 省略2
        
    @Retention(RetentionPolicy.SOURCE)
    public @interface LogAutoRecordTestSource { // 省略 3 
    

    controller

    // 我在controller中使用註解,方法如下:
        @GetMapping("/getUserByCommon")
        @LogAutoRecord(methodDesc = "根據統一請求查詢用戶資訊")
        public CommonResponse getUserByCommonRequest(@RequestBody UserRequestDTO userRequestDTO){
            String userById = userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
        @GetMapping("/getUserByCommonTestSource")
        @LogAutoRecordTestSource(methodDesc = "根據統一請求查詢用戶資訊,測試註解屬性source")
        public CommonResponse getUserByCommonTestSource(@RequestBody UserRequestDTO userRequestDTO){
            String userById = userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
        @GetMapping("/getUserByCommonTestClass")
        @LogAutoRecordTestClass(methodDesc = "根據統一請求查詢用戶資訊,測試註解屬性Class")
        public CommonResponse getUserByCommonTestClass(@RequestBody UserRequestDTO userRequestDTO){
            String userById = userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
    

    編譯後的class文件

    // 編譯後的.class 文件
        @GetMapping({"/getUserByCommon"})
        @LogAutoRecord(
            methodDesc = "根據統一請求查詢用戶資訊"
        )
        public CommonResponse getUserByCommonRequest(@RequestBody UserRequestDTO userRequestDTO) {
            String userById = this.userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
    
        @GetMapping({"/getUserByCommonTestSource"})
        public CommonResponse getUserByCommonTestSource(@RequestBody UserRequestDTO userRequestDTO) {
            String userById = this.userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        }
    
        @GetMapping({"/getUserByCommonTestClass"})
        @LogAutoRecordTestClass(
            methodDesc = "根據統一請求查詢用戶資訊,測試註解屬性Class"
        )
        public CommonResponse getUserByCommonTestClass(@RequestBody UserRequestDTO userRequestDTO) {
            String userById = this.userService.getUserById(userRequestDTO.getUserId());
            return CommonResponse.succeed(userById);
        } 
    

    根據編譯後的文件可以看出source標記的編譯後就不見了,class的還在。

  • @Documented註解,是被用來指定自定義註解是否能隨著被定義的java文件生成到JavaDoc文檔當中

  • @Inherited註解,是指定某個自定義註解如果寫在了父類的聲明部分,那麼子類的聲明部分也能自動擁有該註解

    @Inherited註解只對那些@Target被定義為ElementType.TYPE的自定義註解起作用。

3. 獲取註解

  • 給出指定包

    public class GetAnnotation {
        public static void getAnnotation() {
            // 要掃描的包
            String packageName = "com.zhoust.fastdome.business.controller";
            Reflections f = new Reflections(packageName);
            // 獲取掃描到的標記註解的集合
            Set<Class<?>> set = f.getTypesAnnotatedWith(LogAutoRecord.class);
            for (Class<?> c : set) {
                // 循環獲取標記的註解
                LogAutoRecord annotation = c.getAnnotation(LogAutoRecord.class);
                // 列印註解中的內容
                System.out.println(annotation.methodDesc());
            }
        }
    }
    

    使用Reflections反射需要導入反射包,或者自己使用java自帶的反射包

            <dependency>
                <groupId>org.reflections</groupId>
                <artifactId>reflections</artifactId>
                <version>0.9.11</version>
            </dependency>
    
  • aop切面 中獲取註解的對象

    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String methodName = method.getName();
            LogAutoRecord annotation = method.getAnnotation(LogAutoRecord.class);
            String methodDesc = "記錄日誌";
            if(null != annotation){
                methodDesc = annotation.methodDesc();
            }
    

活到老學到老

Tags: