Java注解(Annotation)原理详解
2018-03-07 14:38
796 查看
from:http://blog.csdn.net/lylwo317/article/details/52163304
首先写一个简单的自定义注解小程序
然后在Main函数中解析注解
下面将围绕上面的代码来研究Java注解(Annotation)的实现原理
可以看到HelloAnnotation注解的实例是jvm生成的动态代理类的对象。
这个运行时生成的动态代理对象是可以导出到文件的,方法有两种
在代码中加入System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
在运行时加入jvm 参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
我们使用第一种如下:
然后运行程序。
可以看到,已经导出了运行时生成的代理类,而且每个分别实现了一个接口。
HelloAnnotation的动态代理类是$Proxy3.class,Intellij自带了反编译工具,直接双击打开,得到如下的Java代码:
从第一行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类。
我们接着看一下HelloAnnotation的字节码
HelloAnnotation就是继承了Annotation的接口。再看第10行,flag字段中,我们可以看到,有个ACC_ANNOTATION标记,说明是一个注解,所以注解本质是一个继承了Annotation的特殊接口。
而Annotation接口声明了以下方法。
这些方法,已经被$Proxy3实现了。(这就是动态代理的机制)
$Proxy3.java
从上面不难看出,say方法最终会执行(String)super.h.invoke(this, m3, (Object[])null);,而这其中的h对象类型就是InvocationHandler接口的某个实现类
断点调试,看看InvocationHandler具体实现类是哪个。
可以看到h对象是AnnotationInvocationHandler的实例,让我们来看看该实现类的invoke方法。
我们直接从invoke方法第一行开始单步调试,看看invoke方法是如何处理我们annotation.say()方法的调用的。
可以看到,say方法的返回值是从一个Map中获取到的。这个map以key(注解方法名)—value(注解方法对应的值)存储TestMain类上的注解
那memberValues这个Map对象是怎么生成的,继续调试
通过方法调用栈找到memberValues的本源。
‘do,it’是在parseMemberValue()方法中获取的,我们继续跟进parseMemberValue()方法
调用完parseConst方法,然后返回到parseMemberValue()方法
可以看到获取的就是我们定义在TestMain类上注解的say的值——“Do it!”
这里可以通过javap -verbose TestMain查看TestMain字节码中的常量池。
到此为止,say方法就完成了。
首先写一个简单的自定义注解小程序
//先自定义一个运行时注解 @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生成的动态代理类的对象。
这个运行时生成的动态代理对象是可以导出到文件的,方法有两种
在代码中加入System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
在运行时加入jvm 参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
我们使用第一种如下:
@HelloAnnotation(say = "Do it!") public class TestMain { public static void main(String[] args) { /* 设置此系统属性,让JVM生成的Proxy类写入文件.保存路径为:com/sun/proxy(如果不存在请手工创建) */ System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//获取TestMain类上的注解对象 System.out.println(annotation.say());//调用注解对象的say方法,并打印到控制台 } }
然后运行程序。
可以看到,已经导出了运行时生成的代理类,而且每个分别实现了一个接口。
HelloAnnotation的动态代理类是$Proxy3.class,Intellij自带了反编译工具,直接双击打开,得到如下的Java代码:
public final class $Proxy3 extends Proxy implements HelloAnnotation { private static Method m3; private static Method m1; private static Method m0; private static Method m4; private static Method m2; public $Proxy3(InvocationHandler var1) throws { super(var1); } 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 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 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); } } 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 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); } } static { try { m3 = Class.forName("HelloAnnotation").getMethod("say", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")}); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m4 = Class.forName("HelloAnnotation").getMethod("annotationType", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
从第一行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类。
我们接着看一下HelloAnnotation的字节码
$ javap -verbose HelloAnnotation public interface HelloAnnotation extends java.lang.annotation.Annotation SourceFile: "HelloAnnotation.java" RuntimeVisibleAnnotations: 0: #11(#12=[e#13.#14]) 1: #15(#12=e#16.#17) minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool: #1 = Class #18 // 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 HelloAnnotation #19 = Utf8 java/lang/Object #20 = Utf8 java/lang/annotation/Annotation { public abstract java.lang.String say(); flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: s#7}
HelloAnnotation就是继承了Annotation的接口。再看第10行,flag字段中,我们可以看到,有个ACC_ANNOTATION标记,说明是一个注解,所以注解本质是一个继承了Annotation的特殊接口。
而Annotation接口声明了以下方法。
public interface Annotation { boolean equals(Object var1); int hashCode(); String toString(); Class<? extends Annotation> annotationType(); }
这些方法,已经被$Proxy3实现了。(这就是动态代理的机制)
小结
现在我们知道了HelloAnnotation注解(接口)是一个继承了Annotation接口的特殊接口,而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy3,该类就是HelloAnnotation注解(接口)的具体实现类。二、动态代理类$Proxy3如何处理annotation.say()方法的调用
无论是否了解动态代理,这里只需要明确一点,动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。我们可以看看源码$Proxy3.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); } } .... }
从上面不难看出,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 = 6182022883658399397L; 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 -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 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()方法的调用的。
可以看到,say方法的返回值是从一个Map中获取到的。这个map以key(注解方法名)—value(注解方法对应的值)存储TestMain类上的注解
那memberValues这个Map对象是怎么生成的,继续调试
通过方法调用栈找到memberValues的本源。
‘do,it’是在parseMemberValue()方法中获取的,我们继续跟进parseMemberValue()方法
public static Object parseMemberValue(Class<?> var0, ByteBuffer var1, ConstantPool var2, Class<?> var3) { Object var4 = null; byte var5 = var1.get(); switch(var5) { case 64: var4 = parseAnnotation(var1, var2, var3, true); break; case 91: return parseArray(var0, var1, var2, var3); case 99: var4 = parseClassValue(var1, var2, var3); break; case 101: return parseEnumValue(var0, var1, var2, var3); default: var4 = parseConst(var5, var1, var2); //此处会调用parseConst方法,继续跟进到parseConst方法 } if(!(var4 instanceof ExceptionProxy) && !var0.isInstance(var4)) { var4 = new AnnotationTypeMismatchExceptionProxy(var4.getClass() + "[" + var4 + "]"); } return var4; } private static Object parseConst(int var0, ByteBuffer var1, ConstantPool var2) { int var3 = var1.getShort() & '\uffff'; switch(var0) { case 66: return Byte.valueOf((byte)var2.getIntAt(var3)); case 67: return Character.valueOf((char)var2.getIntAt(var3)); case 68: return Double.valueOf(var2.getDoubleAt(var3)); case 70: return Float.valueOf(var2.getFloatAt(var3)); case 73: return Integer.valueOf(var2.getIntAt(var3)); case 74: return Long.valueOf(var2.getLongAt(var3)); case 83: return Short.valueOf((short)var2.getIntAt(var3)); case 90: return Boolean.valueOf(var2.getIntAt(var3) != 0); case 115: return var2.getUTF8At(var3); //memberValues是通过常量池获取到,这里的var3就是常量池中的序号。 default: throw new AnnotationFormatError("Invalid member-value tag in annotation: " + var0); } }
调用完parseConst方法,然后返回到parseMemberValue()方法
可以看到获取的就是我们定义在TestMain类上注解的say的值——“Do it!”
这里可以通过javap -verbose TestMain查看TestMain字节码中的常量池。
$ javap -verbose TestMain 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."<init>":()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! //这里就是Do it #30 = NameAndType #12:#13 // "<init>":()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
到此为止,say方法就完成了。
总结
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。相关文章推荐
- Java注解(Annotation)原理详解
- Java注解(Annotation)原理详解
- Java注解(Annotation)原理详解
- Java注解之Annotation详解
- 高阶Java-Java注解 Java annotation 使用详解
- JAVAWEB开发之SpringMVC详解(一)——SpringMVC的框架原理、架构简介、与mybatis整合和注解方式的使用、
- Java注解(Annotation)详解
- Java注解Annotation详解
- java中注解机制及其原理的详解
- Java学习之注解Annotation实现原理
- Java注解(Annotation)详解(四)——注解反射生成SQL语句
- Java学习之注解Annotation实现原理
- java.lang.annotation-jdk的注解原理与使用
- Java 注解详解 (annotation)
- Java注解Annotation详解
- Java注解Annotation详解
- Java注解(Annotation)详解
- java基础-注解Annotation原理和用法
- Java注解之Annotation详解
- Java注解Annotation详解