When using annotations, you might hear people say that annotations are actually an interface. Ever wonder why? Today let’s look at the underlying principles of a wave of annotation implementations and get a deeper look at the implementation of annotations.
Understand the annotation
First of all is not to know what is the annotation, in this does not describe, you can directly Google or Baidu to understand.
All annotations directly the parent interface is Java. Lang. The annotation. The annotation, the understanding, we can directly see the annotation interface classesAbove: a generic interface for all annotation type extensions, also note that the interface itself does not define an annotation type.
Annotation implementation Analysis
The example is essential for analysis. First let’s create a simple annotation @Testannotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation {
String name(a) default "null";
}
Copy the code
And use
@TestAnnotation(name = "main")
public class Main {
public static void main(String[] args) { TestAnnotation annotation = Main.class.getAnnotation(TestAnnotation.class); System.out.println(annotation.name()); }}Copy the code
How do you see the implementation of the corresponding annotation?
So let’s break debug and get the annotation object
It turns out that it’s actually a proxy object $Proxy1.
So how is this proxy object fetched?
1, getAnnotation(Class annotationClass), obtain annotation instances by type.
Main.class.getAnnotation(TestAnnotation.class)
Copy the code
AnnotationData AnnotationData ();
private Class.AnnotationData annotationData(a) {
while (true) { // Retry cycle
Class.AnnotationData annotationData = this.annotationData;
int classRedefinedCount = this.classRedefinedCount;
if(annotationData ! =null &&
annotationData.redefinedCount == classRedefinedCount) {
return annotationData;
}
// null or old annotationData -> create new instance
Class.AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
// Compare old and new data
if (Class.Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
// Return the new AnnotationData
returnnewAnnotationData; }}}Copy the code
CreateAnnotationData (int classRedefinedCount);
Annotations(byte[] var0, ConstantPool var1, Class<? > var2), which is used to parse annotations. This step uses the index resolution of the bytecode constant pool. When the constant is resolved, a member attribute key/value pair is generated as an input parameter in the next step.
5, AnnotationParser# annotationForMap (final Class <? Extends Annotation> VAR0, final Map<String, Object> VAR1), a dynamic proxy class for generating annotations.
public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run(a) {
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, newAnnotationInvocationHandler(var0, var1)); }}); }Copy the code
From the creation method, we can know:
Proxy#newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler h);Copy the code
Eventually generate a JDK dynamic proxies, and the instance is AnnotationInvocationHandler InvocationHandler, we can see its member variables, construction method and invoke method to realize InvocationHandler interface: Find it, analyze a wave:
class AnnotationInvocationHandler implements InvocationHandler.Serializable {
private static final long serialVersionUID = 6182022883658399397L;
// The type of the current annotation
private final Class<? extends Annotation> type;
// Mapping the name and value of the annotated member attribute
private final Map<String, Object> memberValues;
/**
* 代码忽略
*/
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
// Use VAR7 to mark the four methods in the Annotation
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; }}// If the methods currently called are the four methods in the Annotation,
/ / AnnotationInvocationHandler instance has been defined in the implementation of these methods, direct call.
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
// The custom method handles getting the value of the attribute from our annotation map.
Object var6 = this.memberValues.get(var4);
// Check the validity of the value
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) {
// Clone returns the content
var6 = this.cloneArray(var6);
}
returnvar6; }}}}Copy the code
Now that we know that the underlying annotation uses the JDK native Proxy, we can view the source code of the Proxy class. There are two ways to output the source code of the Proxy class:
- Through the Java System property set System. SetProperty (” sun. Misc. ProxyGenerator. SaveGeneratedFiles “, “true”);
- Through the -d parameter specifies, in fact, about 1 parameter is: – Dsun. Misc. ProxyGenerator. SaveGeneratedFiles = true
Choose one of the two ways.
After running, two proxy classes are generated in the root directory
$Proxy0 implements Retention interface
$Proxy1 implements the TestAnnotation interface
public final class $Proxy1 extends Proxy implements TestAnnotation { private static Method m1; private static Method m3; private static Method m2; private static Method m4; private static Method m0; public $Proxy1(InvocationHandler var1) throws { super(var1); } public final Boolean equals(Object VAR1) throws {···} public final String name() 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 String toString() throws {···} public final Class annotationType() throws {···} public final int HashCode () throws {···} // The static code block instantiates the method instance, using the method name directly from the map. // This is a very efficient way to do this. static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("com.mrlsm.spring.demo.TestAnnotation").getMethod("name"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("com.mrlsm.spring.demo.TestAnnotation").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()); }}}Copy the code
We found that the static code block in the $Proxy1 proxy class instantiates the method instance, using the method name directly from the map, not using reflection to get the value, but directly calling, very efficient processing way.
summary
An Annotation is an interface that implements the Annotation interface and then returns a Proxy $Proxy object when calling the getAnnotation() method. This is created using the JDK dynamic Proxy. When using the Proxy’s newProxyInstance method, An example of the incoming interface and InvocationHandler (namely AnotationInvocationHandler), finally returned to a broker instance, for the value of the acquisition and operation.