The underlying implementation of annotations in JDK

premise

It has been almost three years using Java. Annotation is a common type, especially in some frameworks, annotation will be widely used for component identification, configuration or strategy. However, I haven't explored in depth what annotations in JDK are and how the underlying layer is implemented? So I referred to some materials and made a slightly detailed analysis.

JDK annotation description

Refer to jls-9.6 in javase-8 to describe the annotation as follows:

The statement of the note is as follows:

{InterfaceModifier} @ interface Identifier AnnotationTypeBody

接口修饰符 @ interface 注解标识符 注解类型的内容

Of which:

Since the parent interface of all annotation types is Java lang.annotation. Annotation, let's take a look at the documentation of the annotation interface:

The description of annotation in javase-8 document is similar to that in jls-9.6, but finally points out the compatibility consideration of repeatable annotation, which is in jdk1 8 is implemented by the meta annotation @ repeatable. The following is based on the last version of jdk8, java version 1.8 0_ 181 explore the underlying implementation of annotations in JDK.

Research on annotation implementation

We first define a very simple counter annotation as follows:

package club.throwable.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface Counter {

	int count() default 0;
}

Let's directly use @ counter annotation to visually observe the type of @ counter instance:

@Counter(count = 1)
public class Main {

	public static void main(String[] args) throws Exception{
		Counter counter = Main.class.getAnnotation(Counter.class);
		System.out.println(counter.count());
	}
}

@The counter instance is a proxy class of JDK (and the instance of invocationhandler is sun.reflect.annotation.annotationinvocationhandler, which is an available class in the sun package with the modifier of default). To verify this, we use the decompile command of JDK to view the bytecode of @ counter:

javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\annotation\Counter.class

@The decoded bytecode of counter is as follows:

Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/annotation/Counter.class
  Last modified 2018-10-6; size 487 bytes
  MD5 checksum 83cee23f426e5b51a096281068d8b555
  Compiled from "Counter.java"
public interface club.throwable.annotation.Counter extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC,ACC_INTERFACE,ACC_ABSTRACT,ACC_ANNOTATION
Constant pool:
   #1 = Class              #19            // club/throwable/annotation/Counter
   #2 = Class              #20            // java/lang/Object
   #3 = Class              #21            // java/lang/annotation/Annotation
   #4 = Utf8               count
   #5 = Utf8               ()I
   #6 = Utf8               AnnotationDefault
   #7 = Integer            0
   #8 = Utf8               SourceFile
   #9 = Utf8               Counter.java
  #10 = Utf8               RuntimeVisibleAnnotations
  #11 = Utf8               Ljava/lang/annotation/Retention;
  #12 = Utf8               value
  #13 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #14 = Utf8               RUNTIME
  #15 = Utf8               Ljava/lang/annotation/Documented;
  #16 = Utf8               Ljava/lang/annotation/Target;
  #17 = Utf8               Ljava/lang/annotation/ElementType;
  #18 = Utf8               TYPE
  #19 = Utf8               club/throwable/annotation/Counter
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/annotation/Annotation
{
  public abstract int count();
    descriptor: ()I
    flags: ACC_PUBLIC,ACC_ABSTRACT
    AnnotationDefault:
      default_value: I#7}
SourceFile: "Counter.java"
RuntimeVisibleAnnotations:
  0: #11(#12=e#13.#14)
  1: #15()
  2: #16(#12=[e#17.#18])

If you are familiar with bytecode, you can intuitively get the following information:

Since the annotation is finally transformed into an interface, and the annotation member attributes defined in the annotation will be transformed into abstract methods, how do these annotation member attributes be assigned? The answer is: generate a dynamic proxy class for the interface corresponding to the annotation. To put it directly, Java generates an instance that implements the "annotation corresponding interface" through a dynamic proxy, and the proxy class instance implements the "method corresponding to the annotation member attribute". This step is similar to the assignment process of the "annotation member attribute", In this way, you can get the member properties of the annotation through reflection when the program is running (the annotation must be visible at run time, that is, @ retention (retentionpolicy. Runtime) is used, and you need to understand the JDK native dynamic agent and reflection related content).

Annotation corresponding dynamic proxy class instance

It has been pointed out in the above that the bottom implementation of the annotation is a JDK dynamic proxy class, and the generation process of this dynamic proxy class can be tracked through debug. Here are the daily accounts of the whole process tracked by the author:

Pay attention to step 5 and post its source code:

    public static Annotation annotationForMap(final Class<? extends Annotation> var0,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));
            }
        });
    }

Familiar with JDK dynamic agent, the code here should look very simple, that is, to generate a standard JDK dynamic agent, and the instance of invocationhandler is annotationinvocationhandler. You can see its member variables, construction methods and invoke methods that implement the invocationhandler interface:

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 this.hashCodeImpl();
            case 2:
                return this.type;
            default:
			    //利用方法名称从memberValues获取成员属性的赋值
                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;
                }
            }
        }
    }
//忽略其他方法	

It should be noted that the member variable map < string, Object > membervalues of annotationinvocationhandler stores the mapping of the name and value of the annotated member attribute. The name of the annotated member attribute actually corresponds to the name of the abstract method in the interface. For example, after the @ counter annotation defined above generates the proxy class, The membervalues property in its annotationinvocationhandler instance holds the key value pair count = 1.

Now that we know that the JDK native proxy is used at the bottom of the annotation, we can directly output the proxy class to the specified directory to analyze the source code of the proxy class. There are two ways to output the source code of the proxy class:

Here, use mode 1 to modify the main method used above:

	public static void main(String[] args) throws Exception {
		System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
		Counter counter = Main.class.getAnnotation(Counter.class);
		System.out.println(counter.count());
	}

After execution, there is one more directory in the project:

Where $proxy0 is the dynamic proxy class corresponding to the @ retention annotation, and $proxy1 is the dynamic proxy class corresponding to our @ counter. Of course, $proxyn may be generated if there are more annotations. Next, let's look directly at the source code of $proxy1:

public final class $Proxy1 extends Proxy implements Counter {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    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});
        } 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 int count() throws  {
        try {
            return (Integer)super.h.invoke(this,m3,(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 int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this,m0,(Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getmethod("equals",Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getmethod("toString");
            m3 = Class.forName("club.throwable.annotation.Counter").getmethod("count");
            m4 = Class.forName("club.throwable.annotation.Counter").getmethod("annotationType");
            m0 = Class.forName("java.lang.Object").getmethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Obviously, $proxy1 implements the counter interface. In the last part of the code, it uses static code blocks to instantiate method instances of member methods. These methods are cached in the previous code. When calling member methods, they are directly delegated to the invocationhandler (annotationinvocationhandler) instance to complete the call. When analyzing the annotationinvocationhandler, we see that it only uses the name of the method to match the result of the member method from the map. Therefore, the calling process is not a reflection call, but a direct call. The efficiency is similar to obtaining the value from the map instance through the key. It is very efficient.

Summary

Now that we know the underlying principle of annotation, we can write an "annotation interface" and invocationhandler implementation to simply simulate the whole process. Define an interface first:

public interface CounterAnnotation extends Annotation {

	int count();
}

Simple implementation of invocationhandler:

public class CounterAnnotationInvocationHandler implements InvocationHandler {

	private final Map<String,Object> memberValues;
	private final Class<? extends Annotation> clazz;

	public CounterAnnotationInvocationHandler(Map<String,Object> memberValues,Class<? extends Annotation> clazz) {
		this.memberValues = memberValues;
		this.clazz = clazz;
	}

	@Override
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
		String methodName = method.getName();
		Object value;
		switch (methodName) {
			case "toString":
				value = super.toString();
				break;
			case "hashCode":
				value = super.hashCode();
				break;
			case "equals":
				value = super.equals(args[0]);
				break;
			case "annotationType":
				value = clazz;
				break;
			default:
				value = memberValues.get(methodName);
		}
		return value;
	}
}

Write a main method:

public class CounterAnnotationMain {

	public static void main(String[] args) throws Exception{
		//这里模拟了注解成员属性从常量池解析的过程
		Map<String,Object> values = new HashMap<>(8);
		values.put("count",1);
		//生成代理类
		CounterAnnotation proxy = (CounterAnnotation)Proxy.newProxyInstance(CounterAnnotationMain.class.getClassLoader(),new Class[]{CounterAnnotation.class},new CounterAnnotationInvocationHandler(values,CounterAnnotation.class));
		System.out.println(proxy.count());
	}
}
//运行后控制台输出:1

(c-1-d e-20181006)

The official account of Technology (Throwable Digest), which is not regularly pushed to the original technical article (never copied or copied):

Entertainment official account ("sand sculpture"), select interesting sand sculptures, videos and videos, push them to relieve life and work stress.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>