<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文讀懂Annotation

          共 7586字,需瀏覽 16分鐘

           ·

          2021-03-26 20:51

          點擊上方老周聊架構(gòu)關(guān)注我



          一、什么是注解

          根據(jù)wikipedia中介紹:

          In the Java computer programming language, an annotation is a form of syntactic metadata that can be added to Java source code. Classes, methods, variables, parameters and Java packages may be annotated. Like Javadoc tags, Java annotations can be read from source files. Unlike Javadoc tags, Java annotations can also be embedded in and read from Java class files generated by the Java compiler. This allows annotations to be retained by the Java virtual machine at run-time and read via reflection. It is possible to create meta-annotations out of the existing ones in Java.

          翻譯中文則是:

          Java 注解又稱 Java 標注,是 JDK5.0 版本開始支持加入源代碼的特殊語法元數(shù)據(jù) 。
          Java 語言中的類、方法、變量、參數(shù)和包等都可以被標注。和 Javadoc 不同,Java 標注可以通過反射獲取標注內(nèi)容。在編譯器生成類文件時,標注可以被嵌入到字節(jié)碼中。Java 虛擬機可以保留標注內(nèi)容,在運行時可以獲取到標注內(nèi)容。當然它也支持自定義 Java 標注。

          這定義已經(jīng)夠清晰了,但你可能有個疑問,老周啊,通過反射獲取標注內(nèi)容,反射在哪體現(xiàn)啊。別問,一問又是要看底層源碼了。

          既然這樣,那我們就來看下 JDK java.lang.annotation 包的結(jié)構(gòu):


          看見帶 @ 標識的沒,一共有 6 個,所以 JDK 源碼里定義了 6 個注解。

          • @Document

          • @Target

          • @Retention

          • @Inherited

          • @Native

          • @Repeatable


          其中前 4 個是元注解。

          等一等,元注解又是什么?不急,我們來看下一節(jié)。

          二、元注解

          元注解的作用就是負責注解其它注解,它們被用來提供對其它 annotation 類型作說明。

          我們拿 @Document 元注解來說吧。

          @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {}

          不管是這里的 Documented 還是我們自定義的注解,都需要使用 @interface 標識,使用了這個標識,會自動繼承 java.lang.annotation.Annotation 接口,由編譯程序自動完成其它細節(jié)。你又可能會說了,為啥是這樣的標識就會自動繼承這個接口呀。額,這是人家的事先約定的規(guī)范,如果你是 JDK 源碼的開發(fā)人員,也可以自己定義其它標識,亦或是寫了一套比注解還好用的規(guī)范呢。

          在定義注解時,不能繼承其它的注解或接口。@interface 用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數(shù)。方法的名稱就是參數(shù)的名稱,返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過 default 來聲明參數(shù)的默認值。

          1、Annotation 類型里面的參數(shù)該如何設(shè)定

          • 只能用 public 或默認(default)這兩個修飾訪問權(quán)限。例如 String value(); 這里把方法設(shè)為 defaul 默認類型。

          • 參數(shù)成員只能用【char、byte、short、int、long、float、double、boolean】八種基本數(shù)據(jù)類型和 String、Enum、Class 和 annotations 等數(shù)據(jù)類型,以及這一些類型的數(shù)組。例如 String value(); 這里的參數(shù)成員就為 String。

          • 如果只有一個參數(shù)成員,最好把參數(shù)名稱設(shè)為 “value”,后加小括號。


          2、元注解的用途

          在詳細說這四個元數(shù)據(jù)的含義之前,先來看一個在工作中會經(jīng)常使用到的 @Autowired 注解,此注解中使用到了 @Target、@Retention、@Documented 這三個元注解 。

          @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Autowired {    boolean required() default true;}

          2.1 @Target 元注解

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

          @Target 注解,是專門用來限定某個自定義注解能夠被應用在哪些 Java 元素上面的,標明作用范圍;取值在 java.lang.annotation.ElementType 進行定義的。

          public enum ElementType {    /** 類,接口(包括注解類型)或枚舉的聲明 */    TYPE,
          /** 屬性的聲明 */ FIELD,
          /** 方法的聲明 */ METHOD,
          /** 方法形式參數(shù)聲明 */ PARAMETER,
          /** 構(gòu)造方法的聲明 */ CONSTRUCTOR,
          /** 局部變量聲明 */ LOCAL_VARIABLE,
          /** 注解類型聲明 */ ANNOTATION_TYPE,
          /** 包的聲明 */ PACKAGE,
          /** 作用于類型參數(shù)(泛型參數(shù))聲明 */ TYPE_PARAMETER,
          /** 作用于使用類型的任意語句(不包括class) */ TYPE_USE}

          根據(jù)此處可以知道 @Autowired 注解的作用范圍:

          // 可以作用在 構(gòu)造方法、方法、方法形參、屬性、注解類型 上@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})

          2.2 @Retention 元注解

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

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

          注解的生命周期有三個階段:

          • Java 源文件階段

          • 編譯到 class 文件階段

          • 運行期階段


          同樣使用了 RetentionPolicy 枚舉類型對這三個階段進行了定義:

          public enum RetentionPolicy {    /**     * 注解將被編譯器忽略掉     */    SOURCE,
          /** * 注解將被編譯器記錄在class文件中,但在運行時不會被虛擬機保留,這是一個默認的行為 */ CLASS,
          /** * 注解將被編譯器記錄在class文件中,而且在運行時會被虛擬機保留,因此它們能通過反射被讀取到 */ RUNTIME}

          再詳細描述下這三個階段:

          • 如果被定義為 RetentionPolicy.SOURCE,則它將被限定在 Java 源文件中,那么這個注解即不會參與編譯也不會在運行期起任何作用,這個注解就和一個注釋是一樣的效果,只能被閱讀 Java 文件的人看到;

          • 如果被定義為 RetentionPolicy.CLASS,則它將被編譯到 Class 文件中,那么編譯器可以在編譯時根據(jù)注解做一些處理動作,但是運行時 JVM(Java虛擬機)會忽略它,并且在運行期也不能讀取到;

          • 如果被定義為 RetentionPolicy.RUNTIME,那么這個注解可以在運行期的加載階段被加載到 Class 對象中。那么在程序運行階段,可以通過反射得到這個注解,并通過判斷是否有這個注解或這個注解中屬性的值,從而執(zhí)行不同的程序代碼段。


          注意:實際開發(fā)中的自定義注解幾乎都是使用的 RetentionPolicy.RUNTIME 。

          2.3 @Documented 元注解

          @Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {}

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

          2.4 @Inherited 元注解

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

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

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

          三、如何自定義注解

          上面把注解與元注解說完了,那得實戰(zhàn)一下吧。其實很多人在工作中已經(jīng)用到過了或者自己沒用到過但項目中有用到過。但你有沒有想過自定義注解是怎么獲取到值的,是的沒錯,就是我們開頭講的定義,通過反射。

          這里我就拿一個我們項目中自定義注解的例子來說:

          1、標記日志打印的自定義注解

          @Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface PrintLog {}

          2、定義一個切面,在切面中對使用了 @PrintLog 自定義注解的方法進行環(huán)繞增強通知

          @Component@Aspect@Slf4jpublic class PrintLogAspect {    @Around(value = "@annotation(com.riemann.core.annotation.PrintLog)")    public Object handlerPrintLog(ProceedingJoinPoint joinPoint) throws Throwable {        String clazzName = joinPoint.getSignature().getDeclaringTypeName();        String methodName = joinPoint.getSignature().getName();        Object[] args = joinPoint.getArgs();
          Map<String, Object> nameAndArgs = getFieldsName(this.getClass(), clazzName, methodName, args); log.info("Enter class[{}] method[{}] params[{}]", clazzName, methodName, nameAndArgs);
          Object object = null; try { object = joinPoint.proceed(); } catch (Throwable throwable) { log.error("Process class[{}] method[{}] error", clazzName, methodName, throwable); } log.info("End class[{}] method[{}]", clazzName, methodName); return object; }
          private Map<String, Object> getFieldsName(Class clazz, String clazzName, String methodName, Object[] args) throws NotFoundException { Map<String, Object > map = new HashMap<>(); ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath(clazz); pool.insertClassPath(classPath);
          CtClass cc = pool.get(clazzName); CtMethod cm = cc.getDeclaredMethod(methodName); MethodInfo methodInfo = cm.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); if (attr == null) { // exception } int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1; for (int i = 0; i < cm.getParameterTypes().length; i++) { map.put( attr.variableName(i + pos), args[i]); }
          return map; }}


          3、最后,在 Controller 中的方法上使用 @PrintLog 自定義注解即可;當某個方法上使用了自定義注解,那么這個方法就相當于一個切點,那么就會對這個方法做環(huán)繞(方法執(zhí)行前和方法執(zhí)行后)增強處理。

          @RestControllerpublic class Controller {  @PrintLog  @GetMapping(value = "/user/findUserNameById/{id}", produces = "application/json;charset=utf-8")  public String findUserNameById(@PathVariable("id") int id) {      // 模擬根據(jù)id查詢用戶名      String userName = "公眾號【老周聊架構(gòu)】";      return userName;  }}

          4、在瀏覽器中輸入網(wǎng)址:http://127.0.0.1:8080/api/user/findUserNameById/666 回車后觸發(fā)方法執(zhí)行,發(fā)現(xiàn)控制臺打印了日志

          Enter class[Controller] method[findUserNameById] params[{id=666}]End class[Controller] method[findUserNameById]


          這樣的話,項目中的 Controller 類的請求日志我們不必每個方法都打印一遍了,而且收集日志到日志中心請求的參數(shù)也有具體統(tǒng)一的格式,排查問題也方便了不少。使用自定義注解 + AOP 實現(xiàn)日志的打印,有木有如絲滑般順暢的感覺,哈哈,這樣代碼看著也優(yōu)雅了不少。

          這里要說一下,寫這篇文章的初衷是我一個好哥們在群里提了這么個問題,找不到自定義注解和相關(guān)方法的關(guān)聯(lián)。我第一時間想到的是反射,然后再想想自己項目中的場景,雖說是 AOP 實現(xiàn),但注解是如何通過反射獲取值的呢?AOP 切面織入底層是如何實現(xiàn)的呢?有了這兩點疑問,所以老周才下寫下這篇文章,那接下來兩篇就會對這個兩個疑點進行揭秘,敬請期待。



          歡迎大家關(guān)注我的公眾號【老周聊架構(gòu)】,Java后端主流技術(shù)棧的原理、源碼分析、架構(gòu)以及各種互聯(lián)網(wǎng)高并發(fā)、高性能、高可用的解決方案。

          喜歡的話,點贊、再看、分享三連。

          點個在看你最好看



          瀏覽 93
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天干天干天夜夜爽 | 鸥美超逼视频 | 色婷婷,大香蕉 | 丁香av| 人妻互换一二三区免费 |