Java注解(Annotation)原理详解

Java注解(Annotation)原理详解注解在 Java 中到底是什么样的东西 具体是如何实现的 我想刚刚接触注解的时候大家都会有这个疑分析测试的代码 Target ElementType TYPE Retention RetentionPol RUNTIME public interfaceHel Stringsay default Hi

序言


前期准备

知识方面

工具方面

  1. Intellij 2016

开始分析

首先写一个简单的自定义注解小程序。

先自定义一个运行时注解

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface HelloAnnotation { 
      String say() default "Hi"; }

然后在Main函数中解析注解

@HelloAnnotation(say = "Do it!") public class TestMain { 
      public static void main(String[] args) { HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//获取TestMain类上的注解对象 System.out.println(annotation.say());//调用注解对象的say方法,并打印到控制台 } }

运行程序,输出结果如下:

Do it!

下面将围绕上面的代码来研究Java注解(Annotation)的实现原理


1. 注解对象具体是什么?

首先,我们先在main函数第一行断点,看看HelloAnnotation具体是什么类的对象

注解

可以看到HelloAnnotation注解的实例是jvm生成的动态代理类的对象。

这个运行时生成的动态代理对象是可以导出到文件的,方法有两种

  1. 在代码中加入System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  2. 在运行时加入jvm 参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

这里使用第一种,↓

这里写图片描述

然后运行程序。

这里写图片描述

可以看到,已经导出了运行时生成的代理类。↑

HelloAnnotation的动态代理类是$Proxy1.class,Intellij自带了反编译工具,直接双击打开,得到如下的Java代码

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.sun.proxy; import com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy1 extends Proxy implements HelloAnnotation { 
       private static Method m1; private static Method m2; private static Method m4; private static Method m3; private static Method m0; public $Proxy1(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue(); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Class annotationType() throws { try { return (Class)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String say() throws { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue(); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m4 = Class.forName("com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation").getMethod("annotationType", new Class[0]); m3 = Class.forName("com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation").getMethod("say", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } 

从第14行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类

我们接着看一下HelloAnnotation的字节码

 $ javap -verbose HelloAnnotation Warning: Binary file HelloAnnotation contains com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation Classfile /home/kevin/Workspace/IdeaProjects/JavaLearn/out/production/JavaLearn/com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.class Last modified Aug 6, 2016; size 496 bytes MD5 checksum a6c87ff6ab9050ffaea5 Compiled from "HelloAnnotation.java" public interface com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation extends java.lang.annotation.Annotation minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool: #1 = Class #18 // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation #2 = Class #19 // java/lang/Object #3 = Class #20 // java/lang/annotation/Annotation #4 = Utf8 say #5 = Utf8 ()Ljava/lang/String; #6 = Utf8 AnnotationDefault #7 = Utf8 Hi #8 = Utf8 SourceFile #9 = Utf8 HelloAnnotation.java #10 = Utf8 RuntimeVisibleAnnotations #11 = Utf8 Ljava/lang/annotation/Target; #12 = Utf8 value #13 = Utf8 Ljava/lang/annotation/ElementType; #14 = Utf8 TYPE #15 = Utf8 Ljava/lang/annotation/Retention; #16 = Utf8 Ljava/lang/annotation/RetentionPolicy; #17 = Utf8 RUNTIME #18 = Utf8 com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation #19 = Utf8 java/lang/Object #20 = Utf8 java/lang/annotation/Annotation { public abstract java.lang.String say(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: s#7} SourceFile: "HelloAnnotation.java" RuntimeVisibleAnnotations: 0: #11(#12=[e#13.#14]) 1: #15(#12=e#16.#17) 

看到第7行。很明显,HelloAnnotation就是继承了Annotation的接口。再看第10行,flag字段中,我们可以看到,有个ACC_ANNOTATION标记,说明是一个注解,所以注解本质是一个继承了Annotation的特殊接口。

而Annotation接口声明了以下方法。

package java.lang.annotation; public interface Annotation { 
       boolean equals(Object var1); int hashCode(); String toString(); Class 
      annotationType(); } 

这些方法,已经被$Proxy1实现了。(这就是动态代理的机制)

小结

现在我们知道了HelloAnnotation注解(接口)是一个继承了Annotation接口的特殊接口,而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1,该类就是HelloAnnotation注解(接口)的具体实现类。


2. 动态代理类$Proxy1是如何处理annotation.say()方法的调用?

无论是否了解动态代理,这里只需要明确一点,动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。我们可以看看源码

$Proxy1.java

public final class $Proxy1 extends Proxy implements HelloAnnotation { 
        ..... public final String say() throws { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } .... }

Proxy.java

public class Proxy implements java.io.Serializable { 
        / * the invocation handler for this proxy instance. * @serial */ protected InvocationHandler h;

从上面不难看出,say方法最终会执行(String)super.h.invoke(this, m3, (Object[])null);,而这其中的h对象类型就是InvocationHandler接口的某个实现类

断点调试,看看InvocationHandler具体实现类是哪个。

这里写图片描述

可以看到h对象是AnnotationInvocationHandler的实例。让我们来看看该实现类的invoke方法。

class AnnotationInvocationHandler implements InvocationHandler, Serializable { 
          private static final long serialVersionUID = L; private final Class 
         extends Annotation> type; private final Map<String, Object> memberValues; private transient volatile Method[] memberMethods = null; AnnotationInvocationHandler(Class 
        extends Annotation> var1, Map<String, Object> var2) { 
        Class[] var3 = var1.getInterfaces(); if(var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) { 
        this.type = var1; this.memberValues = var2; } else { throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); } } public Object invoke(Object var1, Method var2, Object[] var3) { 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 -: if(var4.equals("toString")) { var7 = 0; } break; case : if(var4.equals("hashCode")) { var7 = 1; } break; case : if(var4.equals("annotationType")) { var7 = 2; } } switch(var7) { case 0: return this.toStringImpl(); case 1: return Integer.valueOf(this.hashCodeImpl()); case 2: return this.type; default: 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); } return var6; } } } } ....... }

我们直接从invoke方法第一行开始单步调试,看看invoke方法是如何处理我们annotation.say()方法的调用的。

这里再贴一次测试代码,不然就得翻到前面了

@HelloAnnotation(say = "Do it!") public class TestMain { 
        public static void main(String[] args) { HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class); System.out.println(annotation.say()); } }

这里写图片描述

可以看到,say方法的返回值是从一个Map中获取到的。这个map以key(注解方法名)—value(注解方法对应的值)存储TestMain类上的注解

这里写图片描述

我们继续跟进parseMemberValue()方法

这里写图片描述

在parseMemberValue()中会调用parseConst方法,继续跟进到parseConst方法

这里写图片描述

可以看到,memberValues是通过常量池获取到,return var2.getUTF8At(var3);中的var3就是常量池中的序号。继续执行返回到parseMemberValue()方法

这里写图片描述

可以看到获取的就是我们定义在TestMain类上注解的say的值——“Do it!”

这里可以通过javap -verbose TestMain查看TestMain字节码中的常量池

$ javap -verbose TestMain Warning: Binary file TestMain contains com.kevin.java.annotation.runtimeAnnotation.TestMain Classfile /home/kevin/Workspace/IdeaProjects/JavaLearn/out/production/JavaLearn/com/kevin/java/annotation/runtimeAnnotation/TestMain.class Last modified Aug 10, 2016; size 1117 bytes MD5 checksum 610b7176c7dfdad08bcdf7123 Compiled from "TestMain.java" public class com.kevin.java.annotation.runtimeAnnotation.TestMain minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool://常量池 #1 = Methodref #11.#30 // java/lang/Object." 
       
         ":()V 
        #2 = String #31 // sun.misc.ProxyGenerator.saveGeneratedFiles #3 = String #32 // true #4 = Methodref #33.#34 // java/lang/System.setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; #5 = Class #35 // com/kevin/java/annotation/runtimeAnnotation/TestMain #6 = Class #36 // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation #7 = Methodref #37.#38 // java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; #8 = Fieldref #33.#39 // java/lang/System.out:Ljava/io/PrintStream; #9 = InterfaceMethodref #6.#40 // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.say:()Ljava/lang/String; #10 = Methodref #41.#42 // java/io/PrintStream.println:(Ljava/lang/String;)V #11 = Class #43 // java/lang/Object #12 = Utf8 <init> #13 = Utf8 ()V #14 = Utf8 Code #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 Lcom/kevin/java/annotation/runtimeAnnotation/TestMain; #19 = Utf8 main #20 = Utf8 ([Ljava/lang/String;)V #21 = Utf8 args #22 = Utf8 [Ljava/lang/String; #23 = Utf8 annotation #24 = Utf8 Lcom/kevin/java/annotation/runtimeAnnotation/HelloAnnotation; #25 = Utf8 SourceFile #26 = Utf8 TestMain.java #27 = Utf8 RuntimeVisibleAnnotations #28 = Utf8 say #29 = Utf8 Do it! #30 = NameAndType #12:#13 // " 
       
         ":()V 
        #31 = Utf8 sun.misc.ProxyGenerator.saveGeneratedFiles #32 = Utf8 true #33 = Class #44 // java/lang/System #34 = NameAndType #45:#46 // setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; #35 = Utf8 com/kevin/java/annotation/runtimeAnnotation/TestMain #36 = Utf8 com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation #37 = Class #47 // java/lang/Class #38 = NameAndType #48:#49 // getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; #39 = NameAndType #50:#51 // out:Ljava/io/PrintStream; #40 = NameAndType #28:#52 // say:()Ljava/lang/String; #41 = Class #53 // java/io/PrintStream #42 = NameAndType #54:#55 // println:(Ljava/lang/String;)V #43 = Utf8 java/lang/Object #44 = Utf8 java/lang/System #45 = Utf8 setProperty #46 = Utf8 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; #47 = Utf8 java/lang/Class #48 = Utf8 getAnnotation #49 = Utf8 (Ljava/lang/Class;)Ljava/lang/annotation/Annotation; #50 = Utf8 out #51 = Utf8 Ljava/io/PrintStream; #52 = Utf8 ()Ljava/lang/String; #53 = Utf8 java/io/PrintStream #54 = Utf8 println #55 = Utf8 (Ljava/lang/String;)V { public com.kevin.java.annotation.runtimeAnnotation.TestMain(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object." 
       
         ":()V 
        4: return LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/kevin/java/annotation/runtimeAnnotation/TestMain; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #2 // String sun.misc.ProxyGenerator.saveGeneratedFiles 2: ldc #3 // String true 4: invokestatic #4 // Method java/lang/System.setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; 7: pop 8: ldc #5 // class com/kevin/java/annotation/runtimeAnnotation/TestMain 10: ldc #6 // class com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation 12: invokevirtual #7 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation; 15: checkcast #6 // class com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation 18: astore_1 19: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 22: aload_1 23: invokeinterface #9, 1 // InterfaceMethod com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.say:()Ljava/lang/String; 28: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 31: return LineNumberTable: line 13: 0 line 14: 8 line 15: 19 line 16: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 args [Ljava/lang/String; 19 13 1 annotation Lcom/kevin/java/annotation/runtimeAnnotation/HelloAnnotation; } SourceFile: "TestMain.java" RuntimeVisibleAnnotations: 0: #24(#28=s#29) 

仔细看第40行#29 = Utf8 Do it!,可以看到#29与var3的29对应(也就常量池的索引),对应的值就是Do it!

以上就是say方法调用的细节。

总结

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208813.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午10:42
下一篇 2026年3月19日 上午10:43


相关推荐

  • html+css实现漂亮的透明登录页面,HTML实现炫酷登录页面

    html+css实现漂亮的透明登录页面,HTML实现炫酷登录页面承蒙各位小伙伴的支持,鄙人有幸入围了《CSDN2020博客之星》的前200名,现在进入投票环节,如果我平时写的文章和分享对你有用的话,请每天点击一下这个链接,投上你们宝贵的一票吧!谢谢!❤️每一票都是我坚持的动力和力量!https://bss.csdn.net/m/topic/blog_star2020/detail?username=qq_23853743作者:AlbertYang,软件设计师,Java工程师,前端工程师,爱阅读,爱思考,爱编程,爱自由,信奉终生学习,每天学习一点点,就是领.

    2022年4月30日
    343
  • wxpython自定义控件_wxPython 教程(十三) 自定义控件

    wxpython自定义控件_wxPython 教程(十三) 自定义控件本节讲述wxPython自定义控件。GUIToolkits会提供多数常用的部件,比如按钮、文本控件、滚动条、滑块等等。wxPython也会提供很多控件,但若需要更定制化的控件还是需要开发者自己编写。自定义控件通过两种方式创建:一种是通过修改或增强现有控件,另一种是我们从零开始直接创建。超链接控件第一个例子是创建一个超链接控件,我们基于wx.lib.stattext.GenStatic…

    2022年5月11日
    33
  • vim搜索及高亮取消

    vim搜索及高亮取消1.命令模式下,输入:/字符串比如搜索user,输入/user按下回车之后,可以看到vim已经把光标移动到该字符处和高亮了匹配的字符串2.查看下一个匹配,按下n(小写n)3.跳转到上一个匹配,按下N(shift+n)4.搜索后,我们打开别的文件,发现也被高亮了,怎么关闭高亮?命令模式下,输入:nohlsearch也可以:setnohlsearch;当然,可以简写,noh或者setnoh。…

    2026年2月26日
    8
  • C++中list用法详解[通俗易懂]

    C++中list用法详解[通俗易懂]1.关于list容器list是一种序列式容器。list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表,也就是list也具有链表的主要优点,即:在链表的任一位置进行元素的插入、删除操作都是快速的。list的实现大概是这样的:list的每个节点有三个域:前驱元素指针域、数据域和后继元素指针域。前驱元素指针域保存了前驱元素的首地

    2022年7月12日
    30
  • sublime text3激活码 2021【在线注册码/序列号/破解码】

    sublime text3激活码 2021【在线注册码/序列号/破解码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月19日
    100
  • ubuntu 18.04 安装 opencv(踩了很多雷终于整合成一个完整的成功版本)[通俗易懂]

    ubuntu 18.04 安装 opencv(踩了很多雷终于整合成一个完整的成功版本)[通俗易懂]更新源 sudo apt update 安装相关包 sudo apt-get install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev libjasper 报错:无法定位到 libj…

    2022年8月18日
    9

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号