java注解底层详解

2020-06-09  本文已影响0人  7d972d5e05e8

首先上参考文章:
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属性。
这里有几个疑问:

  1. 这个代理类是什么时候生成的呢?
    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同理。

到这里总算把注解类型,在字节码和运行时的处理流程搞清楚了。

上一篇 下一篇

猜你喜欢

热点阅读