<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>

          原來注解是這么實現(xiàn)的啊!

          共 26647字,需瀏覽 54分鐘

           ·

          2021-03-31 13:43

          Java注解是在JDK1.5被引入的技術(shù),配合反射可以在運行期間處理注解,配合apt tool可以在編譯器處理注解,在JDK1.6之后,apt tool被整合到了javac里面。

          什么是注解

          注解其實就是一種標(biāo)記,常常用于代替冗余復(fù)雜的配置(XML、properties)又或者是編譯器進行一些檢查如JDK自帶的Override、Deprecated等,但是它本身并不起任何作用,可以說有它沒它都不影響程序的正常運行,注解的作用在于「注解的處理程序」,注解處理程序通過捕獲被注解標(biāo)記的代碼然后進行一些處理,這就是注解工作的方式。

          在java中,自定義一個注解非常簡單,通過@interface就能定義一個注解,實現(xiàn)如下

          public @interface PrintMsg {
          }

          寫個測試類給他加上我們寫的這個注解吧

          @PrintMsg
          public class AnnotationTest {
              public static void main(String[] args) {
                  System.out.println("annotation test OK!");
              }
          }

          我們發(fā)現(xiàn)寫與不寫這個注解的效果是相同的,這也印證了我們說的注解只是一種「標(biāo)記」,有它沒它并不影響程序的運行。

          元注解

          在實現(xiàn)這個注解功能之前,我們先了解一下元注解。

          元注解:對注解進行注解,也就是對注解進行標(biāo)記,元注解的背后處理邏輯由apt tool提供,對注解的行為做出一些限制,例如生命周期,作用范圍等等。

          @Retention

          用于描述注解的生命周期,表示注解在什么范圍有效,它有三個取值,如下表所示:

          類型作用
          SOURCE注解只在源碼階段保留,在編譯器進行編譯的時候這類注解被抹除,常見的@Override就屬于這種注解
          CLASS注解在編譯期保留,但是當(dāng)Java虛擬機加載class文件時會被丟棄,這個也是@Retention的「默認(rèn)值」。@Deprecated和@NonNull就屬于這樣的注解
          RUNTIME注解在運行期間仍然保留,在程序中可以通過反射獲取,Spring中常見的@Controller、@Service等都屬于這一類

          @Target

          用于描述注解作用的「對象類型」,這個就非常多了,如下表所示:

          類型作用的對象類型
          TYPE類、接口、枚舉
          FIELD類屬性
          METHOD方法
          PARAMETER參數(shù)類型
          CONSTRUCTOR構(gòu)造方法
          LOCAL_VARIABLE局部變量
          ANNOTATION_TYPE注解
          PACKAGE
          TYPE_PARAMETER1.8之后,泛型
          TYPE_USE1.8之后,除了PACKAGE之外任意類型

          @Documented

          將注解的元素加入Javadoc中

          @Inherited

          如果被這個注解標(biāo)記了,被標(biāo)記的類、接口會繼承父類、接口的上面的注解

          @Repeatable

          表示該注解可以重復(fù)標(biāo)記

          注解的屬性

          除了元注解之外,我們還能給注解添加屬性,注解中的屬性以無參方法的形式定義,方法名為屬性名,返回值為成員變量的類型,還是以上述注解為例:

          首先給這個注解加億點點細(xì)節(jié),生命周期改為Runtime,使得運行期存在可以被我們獲取

          @Retention(RetentionPolicy.RUNTIME)
          public @interface PrintMsg {
              int count() default 1;
              String name() default "my name is PrintMsg";
          }

          @PrintMsg(count = 2020)
          public class AnnotationTest {
              public static void main(String[] args) {
                  //通過反射獲取該注解
                  PrintMsg annotation = AnnotationTest.class.getAnnotation(PrintMsg.class);
                  System.out.println(annotation.count());
                  System.out.println(annotation.name());
              }
          }

          輸出如下:

          2020
          my name is PrintMsg

          到這里就有兩個疑問了:

          1. getAnnotation獲取到的是什么?一個實例?注解是一個類?
          2. 我們明明調(diào)用的是count(),name(),但是為什么說是注解的屬性?

          等下聊

          到底什么是注解?

          按照注解的生命周期以及處理方式的不同,通常將注解分為「運行時注解」「編譯時注解」

          • 運行時注解的本質(zhì)是實現(xiàn)了Annotation接口的特殊接口,JDK在運行時為其創(chuàng)建代理類,注解方法的調(diào)用實際是通過AnnotationInvocationHandler的invoke方法,AnnotationInvocationHandler其中維護了一個Map,Map中存放的是方法名與返回值的映射,對注解中自定義方法的調(diào)用其實最后就是用方法名去查Map并且放回的一個過程
          • 編譯時注解通過注解處理器來支持,而注解處理器的實際工作過程由JDK在編譯期提供支持,有興趣可以看看javac的源碼

          運行時注解原理詳解

          之前我們說注解是一種標(biāo)記,只是針對注解的作用而言,而Java語言層面注解到底是什么呢?以JSL中的一段話開頭

          ?

          An annotation type declaration specifies a new annotation type, a special kind of interface type. To distinguish an annotation type declaration from a normal interface declaration, the keyword interface is preceded by an at-sign (@).

          ?

          簡單來說就是,注解只不過是在interface前面加了@符號的特殊接口,那么不妨以PrintMsg.class開始來看看,通過javap反編譯的到信息如下:

          public interface com.hustdj.jdkStudy.annotation.PrintMsg extends java.lang.annotation.Annotation
            minor version: 0
            major version: 52
            flags: (0x2601ACC_PUBLICACC_INTERFACEACC_ABSTRACTACC_ANNOTATION
            this_class: #1                          // com/hustdj/jdkStudy/annotation/PrintMsg
            super_class: #3                         // java/lang/Object
            interfaces: 1, fields: 0, methods: 2, attributes: 2
          Constant pool:
             #1 
          = Class              #2             // com/hustdj/jdkStudy/annotation/PrintMsg
             #2 = Utf8               com/hustdj/jdkStudy/annotation/PrintMsg
             #3 = Class              #4             // java/lang/Object
             #4 = Utf8               java/lang/Object
             #5 = Class              #6             // java/lang/annotation/Annotation
             #6 = Utf8               java/lang/annotation/Annotation
             #7 = Utf8               count
             #8 = Utf8               ()I
             #9 = Utf8               AnnotationDefault
            #10 = Integer            1
            #11 = Utf8               name
            #12 = Utf8               ()Ljava/lang/String;
            #13 = Utf8               my name is PrintMsg
            #14 = Utf8               SourceFile
            #15 = Utf8               PrintMsg.java
            #16 = Utf8               RuntimeVisibleAnnotations
            #17 = Utf8               Ljava/lang/annotation/Retention;
            #18 = Utf8               value
            #19 = Utf8               Ljava/lang/annotation/RetentionPolicy;
            #20 = Utf8               RUNTIME
          {
            public abstract int count();
              descriptor: ()I
              flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
              AnnotationDefault:
                default_value: I#10

            public abstract java.lang.String name();
              descriptor: ()Ljava/lang/String;
              flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
              AnnotationDefault:
                default_value: s#13
          }
          SourceFile: "PrintMsg.java"
          RuntimeVisibleAnnotations:
            0: #17(#18=e#19.#20)

          從第一行就不難看出,注解是一個繼承自Annotation接口的接口,它并不是一個類,那么getAnnotation()拿到的到底是什么呢?不難想到,通過動態(tài)代理生成了代理類,是這樣的嘛?通過啟動參數(shù)-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true或者在上述代碼中添加:

          System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");將通過JDK的proxyGenerator生成的代理類保存下來在com.sun.proxy文件夾下面找到這個class文件,通過javap反編譯結(jié)果如下:

          public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements com.hustdj.jdkStudy.annotation.PrintMsg

          可以看出JDK通過動態(tài)代理實現(xiàn)了一個類繼承我們自定義的PrintMsg接口,由于這個方法字節(jié)碼太長了,看起來頭疼,利用idea自帶的反編譯直接在idea中打開該class文件如下:

          public final class $Proxy1 extends Proxy
              implements PrintMsg
          {

              public $Proxy1(InvocationHandler invocationhandler)
              {
                  super(invocationhandler);
              }

              public final boolean equals(Object obj)
              
          {
                  try
                  {
                      return ((Boolean)super.h.invoke(this, m1, new Object[] {
                          obj
                      })).booleanValue();
                  }
                  catch(Error _ex) { }
                  catch(Throwable throwable)
                  {
                      throw new UndeclaredThrowableException(throwable);
                  }
              }

              public final String name()
              
          {
                  try
                  {
                      return (String)super.h.invoke(this, m3, null);
                  }
                  catch(Error _ex) { }
                  catch(Throwable throwable)
                  {
                      throw new UndeclaredThrowableException(throwable);
                  }
              }

              public final String toString()
              
          {
                  try
                  {
                      return (String)super.h.invoke(this, m2, null);
                  }
                  catch(Error _ex) { }
                  catch(Throwable throwable)
                  {
                      throw new UndeclaredThrowableException(throwable);
                  }
              }

              public final int count()
              
          {
                  try
                  {
                      return ((Integer)super.h.invoke(this, m4, null)).intValue();
                  }
                  catch(Error _ex) { }
                  catch(Throwable throwable)
                  {
                      throw new UndeclaredThrowableException(throwable);
                  }
              }

              public final Class annotationType()
              
          {
                  try
                  {
                      return (Class)super.h.invoke(this, m5, null);
                  }
                  catch(Error _ex) { }
                  catch(Throwable throwable)
                  {
                      throw new UndeclaredThrowableException(throwable);
                  }
              }

              public final int hashCode()
              
          {
                  try
                  {
                      return ((Integer)super.h.invoke(this, m0, null)).intValue();
                  }
                  catch(Error _ex) { }
                  catch(Throwable throwable)
                  {
                      throw new UndeclaredThrowableException(throwable);
                  }
              }

              private static Method m1;
              private static Method m3;
              private static Method m2;
              private static Method m4;
              private static Method m5;
              private static Method m0;

              static 
              {
                  try
                  {
                      m1 = Class.forName("java.lang.Object").getMethod("equals"new Class[] {
                          Class.forName("java.lang.Object")
                      });
                      m3 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("name"new Class[0]);
                      m2 = Class.forName("java.lang.Object").getMethod("toString"new Class[0]);
                      m4 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("count"new Class[0]);
                      m5 = Class.forName("com.hustdj.jdkStudy.annotation.PrintMsg").getMethod("annotationType"new Class[0]);
                      m0 = Class.forName("java.lang.Object").getMethod("hashCode"new Class[0]);
                  }
                  catch(NoSuchMethodException nosuchmethodexception)
                  {
                      throw new NoSuchMethodError(nosuchmethodexception.getMessage());
                  }
                  catch(ClassNotFoundException classnotfoundexception)
                  {
                      throw new NoClassDefFoundError(classnotfoundexception.getMessage());
                  }
              }
          }

          小結(jié)

          至此就解決了第一個疑問了,「所謂的注解其實就是一個實現(xiàn)了Annotation的接口,而我們通過反射獲取到的實際上是通過JDK動態(tài)代理生成的代理類,這個類實現(xiàn)了我們的注解接口」

          AnnotationInvocationHandler

          那么問題又來了,具體是如何調(diào)用的呢?

          $Proxy1的count方法為例

          public final int count()
          {
              try
              {
                  return ((Integer)super.h.invoke(this, m4, null)).intValue();
              }
              catch(Error _ex) { }
              catch(Throwable throwable)
              {
                  throw new UndeclaredThrowableException(throwable);
              }
          }

          跟進super

          public class Proxy implements java.io.Serializable {
              protected InvocationHandler h;
          }

          這個InvocationHandler是誰呢?通過在Proxy(InvocationHandler h)方法上打斷點追蹤結(jié)果如下:

          image.png

          原來我們對于count方法的調(diào)用傳遞給了AnnotationInvocationHandler

          看看它的invoke邏輯

          public Object invoke(Object var1, Method var2, Object[] var3) {
              //var4-方法名
              String var4 = var2.getName();
              Class[] var5 = var2.getParameterTypes();
              if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class{
                  return this.equalsImpl(var3[0]);
              } else if (var5.length != 0) {
                  throw new AssertionError("Too many parameters for an annotation method");
              } else {
                  byte var7 = -1;
                  switch(var4.hashCode()) {
                      case -1776922004:
                          if (var4.equals("toString")) {
                              var7 = 0;
                          }
                          break;
                      case 147696667:
                          if (var4.equals("hashCode")) {
                              var7 = 1;
                          }
                          break;
                      case 1444986633:
                          if (var4.equals("annotationType")) {
                              var7 = 2;
                          }
                  }

                  switch(var7) {
                      case 0:
                          return this.toStringImpl();
                      case 1:
                          return this.hashCodeImpl();
                      case 2:
                          return this.type;
                      default:
                          //因為我們是count方法,走這個分支
                          Object var6 = this.memberValues.get(var4);
                          if (var6 == null) {
                              throw new IncompleteAnnotationException(this.type, var4);
                          } else if (var6 instanceof ExceptionProxy) {
                              throw ((ExceptionProxy)var6).generateException();
                          } else {
                              if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                                  var6 = this.cloneArray(var6);
                              }
               //返回var6
                              return var6;
                          }
                  }
              }
          }

          這個memberValues是啥?

          private final Map<String, Object> memberValues;

          他是一個map,存放的是方法名(String)與值的鍵值對

          這里以count()方法的invoke執(zhí)行為例

          image.png

          可以看到它走了default的分支,從上面的map中取到了,我們所定義的2020,那這個memberValues是什么時候解析出來的呢?

          通過查看方法調(diào)用棧,我們發(fā)現(xiàn)在下圖這個時候countname還沒有賦值

          image.png

          在方法中加入斷點重新調(diào)試得到如下結(jié)果

          image.png

          2020出現(xiàn)了,再跟進parseMemberValue方法中,再次重新調(diào)試

          image.png

          再跟進parseConst方法

          image.png

          康康javap反編譯的字節(jié)碼中的常量池吧

          #71 = Integer            2020

          好巧啊,正好是2020!!

          因此發(fā)現(xiàn)最后是從ConstantPool中根據(jù)偏移量來獲取值的,至此另一個疑問也解決了,我們在注解中設(shè)置的方法,最終在調(diào)用的時候,是從一個以<方法名,屬性值>為鍵值對的map中獲取屬性值,定義成方法只是為了在反射調(diào)用作為參數(shù)而已,所以也可以將它看成屬性吧。

          總結(jié)

          運行時注解的產(chǎn)生作用的步驟如下:

          1. 對annotation的反射調(diào)用使得動態(tài)代理創(chuàng)建實現(xiàn)該注解的一個類
          2. 代理背后真正的處理對象為AnnotationInvocationHandler,這個類內(nèi)部維護了一個map,這個map的鍵值對形式為<注解中定義的方法名,對應(yīng)的屬性名>
          3. 任何對annotation的自定義方法的調(diào)用(拋開動態(tài)代理類繼承自object的方法),最終都會實際調(diào)用AnnotatiInvocationHandler的invoke方法,并且該invoke方法對于這類方法的處理很簡單,拿到傳遞進來的方法名,然后去查map
          4. map中memeberValues的初始化是在AnnotationParser中完成的,是勤快的,在方法調(diào)用前就會初始化好,緩存在map里面
          5. AnnotationParser最終是通過ConstantPool對象從常量池中拿到對應(yīng)的數(shù)據(jù)的,再往下ConstantPool對象就不深入了

          編譯時注解初探

          由于編譯時注解的很多處理邏輯內(nèi)化在Javac中,這里不做過多探討,僅對《深入理解JVM》中的知識點進行梳理和總結(jié)。

          在JDK5中,Java語言提供了對于注解的支持,此時的注解只在程序運行時發(fā)揮作用,但是在JDK6中,JDK新加入了一組插入式注解處理器的標(biāo)準(zhǔn)API,這組API使得我們對于注解的處理可以提前至編譯期,從而影響到前端編譯器的工作!!常用的Lombok就是通過注解處理器來實現(xiàn)的

          「自定義簡單注解處理器」

          實現(xiàn)自己的注解處理器,首先需要繼承抽象類javax.annotation.processing.AbstractProcessor,只有process()方法需要我們實現(xiàn),process()方法如下:

          //返回值表示是否修改Element元素
          public abstract boolean process(Set<? extends TypeElement> annotations,
                                          RoundEnvironment roundEnv)
          ;
          • annotations:這個注解處理器處理的注解集合
          • roundEnv:當(dāng)前round的抽象語法樹結(jié)點,每一個結(jié)點都為一個Element,一共有18種Element包含了Java中 的所有元素:
            • PACKAGE(包)
            • ENUM(枚舉)
            • CLASS(類)
            • ANNOTATION_TYPE(注解)
            • INTERFACE(接口)
            • ENUM_CONSTANT(枚舉常量)
            • FIELD(字段)
            • PARAMETER(參數(shù))
            • LOCAL_VARIABLE(本地變量)
            • EXCEPTION_PARAMETER(異常)
            • METHOD(方法)
            • CONSTRUCTOR(構(gòu)造方法)
            • STATIC_INIT(靜態(tài)代碼塊)
            • INSTANCE_INIT(實例代碼塊)
            • TYPE_PARAMETER(參數(shù)化類型,泛型尖括號中的)
            • RESOURCE_VARIABLE(資源變量,try-resource)
            • MODULE(模塊)
            • OTHER(其他)

          此外還有一個重要的實例變量processingEnv,它提供了上下文環(huán)境,需要創(chuàng)建新的代碼,向編譯器輸出信息,獲取其他工具類都可以通過它

          實現(xiàn)一個簡單的編譯器注解處理器也非常簡單,繼承AbstractProcessor實現(xiàn)process()方法,在process()方法中實現(xiàn)自己的處理邏輯即可,此外需要兩個注解配合一下:

          • @SupportedAnnotationTypes:該注解處理器處理什么注解
          • @SupportedSourceVersion:注解處理器支持的語言版本

          「實例」

          @SupportedAnnotationTypes("com.hustdj.jdkStudy.annotation.PrintMsg")
          @SupportedSourceVersion(SourceVersion.RELEASE_8)
          public class PrintNameProcessor extends AbstractProcessor {

              @Override
              public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
                  Messager messager = processingEnv.getMessager();
                  for (Element element : roundEnv.getRootElements()) {
                      messager.printMessage(Diagnostic.Kind.NOTE,"my name is "+element.toString());
                  }
                  //不修改語法樹,返回false
                  return false;
              }
          }

          輸出如下:

          G:\ideaIU\ideaProjects\cookcode\src\main\java>javac com\hustdj\jdkStudy\annotation\PrintMsg.java

          G:\ideaIU\ideaProjects\cookcode\src\main\java>javac com\hustdj\jdkStudy\annotation\PrintNameProcessor.java

          G:\ideaIU\ideaProjects\cookcode\src\main\java>javac -processor com.hustdj.jdkStudy.annotation.PrintNameProcessor com\hustdj\jdkStudy\annotation\AnnotationTest.java
          警告: 來自注釋處理程序 'com.hustdj.jdkStudy.annotation.PrintNameProcessor' 的受支持 source 版本 'RELEASE_8' 低于 -source '1.9'
          注: my name is com.hustdj.jdkStudy.annotation.AnnotationTest
          1 個警告


          來源: cnblogs.com/danzZ/p/14142814.html

          作者: 南風(fēng)知我不易

          --完--
          推薦閱讀:
          怎么接私貨?這個渠道你100%有用!請收藏!

          在看 
          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  www.caopeng | 色哟哟哟 入口国产精品 | 无毛一线逼 | 国产网红女主播操逼 | 国产看逼 |