java注解底层详解
首先上参考文章:
JAVA 注解的基本原理
Java字节码方法表与属性表深度剖析
这篇写的太好了,醍醐灌顶。
文章具体内容不说了。这里主要看下我本地实现的注解,看下字节码。
注解源码:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
String value() default "hahaha";
int age() default 18;
}
从这里注解的元注解就能看出,我这个自定义注解生命周期到RUNTIME,注解对象是TYPE。
然后我这个注解作用于B.java,如下:
@MyAnnotation(value = "张三", age = 19)
public class B {
}
当然非常简单的源码,我们的目的是研究字节码,不要让太多业务代码影响分析字节码。就这么简单的源码,它的字节码可不简单,一大堆。。。
先简单看下这个自定义注解,它的字节码长什么样子。命令:javap -c -l MyAnnotation
Compiled from "MyAnnotation.java"
public interface com.example.demo.spring.MyAnnotation extends java.lang.annotation.Annotation {
public abstract java.lang.String value();
public abstract int age();
}
这个是简单形式,我们看下更原始的字节码,使用:javap -verbose MyAnnotation
Classfile /Users/xxxx/Documents/wacaiCode/demo/target/classes/com/example/demo/spring/MyAnnotation.class
Last modified 2020-6-9; size 502 bytes
MD5 checksum 18c1da44f2a8d1680350cd23b53c812d
Compiled from "MyAnnotation.java"
public interface com.example.demo.spring.MyAnnotation extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #2 // com/example/demo/spring/MyAnnotation
#2 = Utf8 com/example/demo/spring/MyAnnotation
#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 value
#8 = Utf8 ()Ljava/lang/String;
#9 = Utf8 AnnotationDefault
#10 = Utf8 hahaha
#11 = Utf8 age
#12 = Utf8 ()I
#13 = Integer 18
#14 = Utf8 SourceFile
#15 = Utf8 MyAnnotation.java
#16 = Utf8 RuntimeVisibleAnnotations
#17 = Utf8 Ljava/lang/annotation/Retention;
#18 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#19 = Utf8 RUNTIME
#20 = Utf8 Ljava/lang/annotation/Target;
#21 = Utf8 Ljava/lang/annotation/ElementType;
#22 = Utf8 TYPE
{
public abstract java.lang.String value();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#10
public abstract int age();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#13}
SourceFile: "MyAnnotation.java"
RuntimeVisibleAnnotations:
0: #17(#7=e#18.#19)
1: #20(#7=[e#21.#22])
现在我们编译上面的B.java,然后看下字节码。
命令:javap -verbose B.class
Classfile /Users/xxxx/Documents/wacaiCode/demo/target/classes/com/example/demo/spring/B.class
Last modified 2020-6-9; size 395 bytes
MD5 checksum 23601b92a71580e1f57c09522e464f1d
Compiled from "B.java"
public class com.example.demo.spring.B
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/example/demo/spring/B
#2 = Utf8 com/example/demo/spring/B
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/example/demo/spring/B;
#14 = Utf8 SourceFile
#15 = Utf8 B.java
#16 = Utf8 RuntimeVisibleAnnotations
#17 = Utf8 Lcom/example/demo/spring/MyAnnotation;
#18 = Utf8 value
#19 = Utf8 张三
#20 = Utf8 age
#21 = Integer 19
{
public com.example.demo.spring.B();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/demo/spring/B;
}
SourceFile: "B.java"
RuntimeVisibleAnnotations:
0: #17(#18=s#19,#20=I#21)
看到字节码的Constant pool里面有个value=张三,age=19的属性了吗?这个是在RUNTIME生命周期下,编译器把它放进字节码里面了。后面RuntimeVisibleAnnotations属性表,就是value=张三,age=19的意思。
上面是自定义注解修饰的类或者字段等字节码信息。下面我们再看下,既然自定义注解是接口,那么获取注解的属性方法,一定会有实现,那么它的实现是啥呢?我们稍微改造下B.java,如下:
@MyAnnotation(value = "张三", age = 19)
public class B {
public static void main(String[] args){
Class<B> bClass = B.class;
MyAnnotation annotation = bClass.getAnnotation(MyAnnotation.class);
int age = annotation.age();
String name = annotation.value();
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们添加了个main方法,里面得到annotation对象,然后调用其属性方法。很明显,这里的annotation对象一定是一个实现类,否则没法调用方法。我们运行main方法,然后设置断点。我们用HSDB工具,来获取annotation对象的class信息。如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.example.demo.spring.MyAnnotation;
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 MyAnnotation {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m5;
private static Method m0;
private static Method m3;
public $Proxy1(InvocationHandler var1) {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.example.demo.spring.MyAnnotation").getMethod("age");
m5 = Class.forName("com.example.demo.spring.MyAnnotation").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.example.demo.spring.MyAnnotation").getMethod("value");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public final String value() {
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) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() {
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 int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() {
try {
return (Class)super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int age() {
try {
return (Integer)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
看到没,自定义的annotation类,通过jdk动态代理生成了一个代理类。它实现了MyAnnotation接口,实现了我们自定义的value和age属性。
这里有几个疑问:
- 这个代理类是什么时候生成的呢?
2.代理类的构造函数里面的InvocationHandler又是什么呢?什么时候初始化它呢?
预知后面,请看下下面这句代码:
MyAnnotation annotation = bClass.getAnnotation(MyAnnotation.class);
跟进去
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
看到最核心的一个方法:annotationData(),它就是我们的核心。
private AnnotationData annotationData() {
while (true) { // retry loop
AnnotationData annotationData = this.annotationData;
int classRedefinedCount = this.classRedefinedCount;
if (annotationData != null &&
annotationData.redefinedCount == classRedefinedCount) {
return annotationData;
}
// null or stale annotationData -> optimistically create new instance
AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
// try to install it
if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
// successfully installed new AnnotationData
return newAnnotationData;
}
}
}
核心方法createAnnotationData(),继续往下看:
private AnnotationData createAnnotationData(int classRedefinedCount) {
Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
Class<?> superClass = getSuperclass();
Map<Class<? extends Annotation>, Annotation> annotations = null;
if (superClass != null) {
Map<Class<? extends Annotation>, Annotation> superAnnotations =
superClass.annotationData().annotations;
for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
Class<? extends Annotation> annotationClass = e.getKey();
if (AnnotationType.getInstance(annotationClass).isInherited()) {
if (annotations == null) { // lazy construction
annotations = new LinkedHashMap<>((Math.max(
declaredAnnotations.size(),
Math.min(12, declaredAnnotations.size() + superAnnotations.size())
) * 4 + 2) / 3
);
}
annotations.put(annotationClass, e.getValue());
}
}
}
if (annotations == null) {
// no inherited annotations -> share the Map with declaredAnnotations
annotations = declaredAnnotations;
} else {
// at least one inherited annotation -> declared may override inherited
annotations.putAll(declaredAnnotations);
}
return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);
}
核心处理逻辑在:
Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
这个方法的参数getRawAnnotations()和getConstantPool(),这两个都是native方法:
getRawAnnotations:获取我们自定义注解类型MyAnnotation的字节码
getConstantPool:获取this Class(B.class)的常量池,因为它里面有注解属性值
继续向下跟踪parseAnnotations方法,看下InvocationHandler是啥?
public static Map<Class<? extends Annotation>, Annotation> parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2) {
if (var0 == null) {
return Collections.emptyMap();
} else {
try {
return parseAnnotations2(var0, var1, var2, (Class[])null);
} catch (BufferUnderflowException var4) {
throw new AnnotationFormatError("Unexpected end of annotations.");
} catch (IllegalArgumentException var5) {
throw new AnnotationFormatError(var5);
}
}
}
继续看parseAnnotations2,
private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(byte[] var0, ConstantPool var1, Class<?> var2, Class<? extends Annotation>[] var3) {
LinkedHashMap var4 = new LinkedHashMap();
ByteBuffer var5 = ByteBuffer.wrap(var0);
int var6 = var5.getShort() & '\uffff';
for(int var7 = 0; var7 < var6; ++var7) {
Annotation var8 = parseAnnotation2(var5, var1, var2, false, var3);
if (var8 != null) {
Class var9 = var8.annotationType();
if (AnnotationType.getInstance(var9).retention() == RetentionPolicy.RUNTIME && var4.put(var9, var8) != null) {
throw new AnnotationFormatError("Duplicate annotation for class: " + var9 + ": " + var8);
}
}
}
return var4;
}
继续看 parseAnnotation2
private static Annotation parseAnnotation2(ByteBuffer var0, ConstantPool var1, Class<?> var2, boolean var3, Class<? extends Annotation>[] var4) {
int var5 = var0.getShort() & '\uffff';
Class var6 = null;
String var7 = "[unknown]";
try {
try {
var7 = var1.getUTF8At(var5);
var6 = parseSig(var7, var2);
} catch (IllegalArgumentException var18) {
var6 = var1.getClassAt(var5);
}
} catch (NoClassDefFoundError var19) {
if (var3) {
throw new TypeNotPresentException(var7, var19);
}
skipAnnotation(var0, false);
return null;
} catch (TypeNotPresentException var20) {
if (var3) {
throw var20;
}
skipAnnotation(var0, false);
return null;
}
if (var4 != null && !contains(var4, var6)) {
skipAnnotation(var0, false);
return null;
} else {
AnnotationType var8 = null;
try {
var8 = AnnotationType.getInstance(var6);
} catch (IllegalArgumentException var17) {
skipAnnotation(var0, false);
return null;
}
Map var9 = var8.memberTypes();
LinkedHashMap var10 = new LinkedHashMap(var8.memberDefaults());
int var11 = var0.getShort() & '\uffff';
for(int var12 = 0; var12 < var11; ++var12) {
int var13 = var0.getShort() & '\uffff';
String var14 = var1.getUTF8At(var13);
Class var15 = (Class)var9.get(var14);
if (var15 == null) {
skipMemberValue(var0);
} else {
Object var16 = parseMemberValue(var15, var0, var1, var2);
if (var16 instanceof AnnotationTypeMismatchExceptionProxy) {
((AnnotationTypeMismatchExceptionProxy)var16).setMember((Method)var8.members().get(var14));
}
var10.put(var14, var16);
}
}
return annotationForMap(var6, var10);
}
}
看最后一行annotationForMap方法:
public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
}
});
}
看到没,熟悉的味道JDK动态代理,传入的核心处理类InvocationHandler就是AnnotationInvocationHandler,运行时刻调用的invoke就是由它来处理。当然这里还只是初始化,暂时不涉及运行时调用。
上面的过程主要流程就是:获取B.java类型的常量池,解析出来annotion的属性和属性值放在LinkedHashMap里面。这里的B类,它解析出来的map一定是:
<value,张三>,<age,19>。然后把它传递到annotationForMap方法的var10参数。然后传递给了AnnotationInvocationHandler的构造方法,参数是var2,var2赋值给了memberValues。
下面我们再看下AnnotationInvocationHandler的invoke方法,这里一定会用到上面说的memberValues。我们看下invoke方法的源码:
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 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;
}
}
}
}
看到
default:
Object var6 = this.memberValues.get(var4);
var4就是方法名称,从上面的Proxy$1可以看出,方法名就是value。memberValues的key恰好就是属性名称,也是value。所以get(value)可不就是“张三”嘛、 属性age同理。
到这里总算把注解类型,在字节码和运行时的处理流程搞清楚了。