在平时项目开发中,注解Annotation是我们常常用到的。比如使用Spring,其中@Autowired、@Component、@Service、@Resource、@Value、@Transactional等都是常用的;又如使用ORM,@Table、@Column等注解也是常用的;
再比如,可以使用注解去标注某个DAO的方法走哪一个数据源,如:
@ReadDataSource - 标注为走读库
@WriteDataSource - 标注为走写库
当然,如果核心链路和非核心链路走的不同的数据源,也可以通过注解标注,然后在不同数据源上进行数据的操作。
本篇文章,孟君将带着大家去看看JDK中注解的基本原理是什么。主要从如下几个方面入手:
一、注解编译之后是什么样子的?
编写一个名字为Permission的注解,用于测试:
package com.wangmengjun.tutorial.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface Permission {
String role() default "NONE";
}
使用javap -c -v Permission.class,查看一下字节码:
Last modified 2019年8月20日; size 521 bytes
MD5 checksum ae529693c5c356c1af56de42b5b78c82
Compiled from "Permission.java"
public interface com.wangmengjun.tutorial.annotation.Permission extends java.lang.annotation.Annotation
minor version: 0
major version: 49
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #1 // com/wangmengjun/tutorial/annotation/Permission
super_class: #3 // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
Constant pool:
#1 = Class #2 // com/wangmengjun/tutorial/annotation/Permission
#2 = Utf8 com/wangmengjun/tutorial/annotation/Permission
#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 role
#8 = Utf8 ()Ljava/lang/String;
#9 = Utf8 AnnotationDefault
#10 = Utf8 NONE
#11 = Utf8 SourceFile
#12 = Utf8 Permission.java
#13 = Utf8 RuntimeVisibleAnnotations
#14 = Utf8 Ljava/lang/annotation/Retention;
#15 = Utf8 value
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 Ljava/lang/annotation/Documented;
#19 = Utf8 Ljava/lang/annotation/Target;
#20 = Utf8 Ljava/lang/annotation/ElementType;
#21 = Utf8 TYPE
{
public abstract java.lang.String role();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#10
"NONE"
}
SourceFile: "Permission.java"
RuntimeVisibleAnnotations:
0: #14(#15=e#16.#17)
java.lang.annotation.Retention(
value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME
)
1: #18()
java.lang.annotation.Documented
2: #19(#15=[e#20.#21])
java.lang.annotation.Target(
value=[Ljava/lang/annotation/ElementType;.TYPE]
)
从上述字节码信息可以看出如下几点内容:
public interface com.wangmengjun.tutorial.annotation.Permission
extends java.lang.annotation.Annotation
{
public abstract java.lang.String role();
descriptor: ()Ljava/lang/String;
flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#10
"NONE"
}
SourceFile: "Permission.java"
RuntimeVisibleAnnotations:
0: #14(#15=e#16.#17)
java.lang.annotation.Retention(
value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME
)
1: #18()
java.lang.annotation.Documented
2: #19(#15=[e#20.#21])
java.lang.annotation.Target(
value=[Ljava/lang/annotation/ElementType;.TYPE]
)
其中,RuntimeVisibleAnnotations可以访问如下页面查看更多信息,https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16
综上所述,上述自定义注解Permission等价于如下代码:
package com.wangmengjun.tutorial.annotation;
import java.lang.annotation.Annotation;
public interface Permission
extends Annotation
{
public abstract String role();
}
那么问题来了,注解对应的方法是如何赋值的呢?接下来,我们通过注解的获取来Debug一下底层调用流程。
二、Debug查看一下底层调用流程
2.1 编写一个注解测试类
package com.wangmengjun.tutorial.annotation;
@Permission(role="Admin")
public class PermissionTest {
public static void main(String[] args) {
Permission permAnnotation = PermissionTest.class.getAnnotation(Permission.class);
System.out.println(permAnnotation.role());
}
}
2.2 debug一下getAnnotation的过程
通过PermissionTest.class.getAnnotation(Permission.class)获取注解Permission实例开始,记录调试过程,如下:
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
其内部会调用parseAnnotation2来处理,方法如下:
private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(
byte[] rawAnnotations,
ConstantPool constPool,
Class<?> container,
Class<? extends Annotation>[] selectAnnotationClasses)
其内部会调用另外一个不同参数的parseAnnotation2来处理,方法如下:
private static Annotation parseAnnotation2(ByteBuffer buf,
ConstantPool constPool,
Class<?> container,
boolean exceptionOnMissingAnnotationClass,
Class<? extends Annotation>[] selectAnnotationClasses)
在这个方法中,主要包含提取AnnotationType以及LinkHashMap类型的注解成员值memberValues。
获取type - AnnotationType信息获取如下:
Annotation Type:
Member types: {role=class java.lang.String}
Member defaults: {role=NONE}
Retention policy: RUNTIME
Inherited: false
memberValues内容如下:
可以看出key在Permission中定义的role,value是PermissionTest中标注的role的值Admin。
然后把type和memberValues作为参数调用方法annotationForMap。
public static Annotation annotationForMap(final Class<? extends Annotation> type,
final Map<String, Object> memberValues)
详细内容如下:
从上述代码可以看到,其主要作用就是生成一个动态代理,其中InvocationHandler的实例为AnnotationInvocationHandler。
从上述调用过程来看,注解底层也是通过动态代理来完成其方法的调用的。
那么问题又来了,$Proxy1是什么鬼,具体有些什么内容呢?
可以通过设置系统变量来保存产生的代理文件,因为我这边使用的JDK12,与JDK8等有变化。具体的参数信息如下:
package com.wangmengjun.tutorial.annotation;
@Permission(role="Admin")
public class PermissionTest {
public static void main(String[] args) {
/**
* 把生成的代理类,输出出来。
* - jdk8 sun.misc.ProxyGenerator.saveGeneratedFiles
* - jdk12 jdk.proxy.ProxyGenerator.saveGeneratedFiles
*/
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Permission permAnnotation = PermissionTest.class.getAnnotation(Permission.class);
System.out.println(permAnnotation.role());
}
}
运行之后,刷新Maven工程,可以发现目录com/sun/proxy(这个目录结构是固定的,如果想将生成的代码存放在自定义目录下,可以使用CGLIB来完成)。其中$Proxy1.class就在其中:
同样使用javap -c -v $Proxy1.class,查看编译后的内容,如下图所示:
使用反编译文件查看,直观的内容如下:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
package com.sun.proxy;
import com.wangmengjun.tutorial.annotation.Permission;
import java.lang.reflect.*;
public final class $Proxy1 extends Proxy
implements Permission
{
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
private static Method m0;
public $Proxy1(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String role()
{
try
{
return (String)super.h.invoke(this, m3, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Class annotationType()
{
try
{
return (Class)super.h.invoke(this, m4, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("com.wangmengjun.tutorial.annotation.Permission").getMethod("role", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.wangmengjun.tutorial.annotation.Permission").getMethod("annotationType", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch (NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch (ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
其中,注解Permission定义的role()实现和annotationType()获取注解类型内容如下:
public final String role()
{
try
{
return (String)super.h.invoke(this, m3, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final Class annotationType()
{
try
{
return (Class)super.h.invoke(this, m4, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
PS:其中$Proxy0是Retention。
public final class $Proxy0 extends Proxy
implements Retention {
... ...
}
三、小结
从上述两个部分,我们已经看到注解的原理基本上有两点:
一个注解定义后,其是一个继承java.lang.annotation.Annotation的接口,这就是Annotation中描述的 - The common interface extended by all annotation types。
/**
* The common interface extended by all annotation types. Note that an
* interface that manually extends this one does <i>not</i> define
* an annotation type. Also note that this interface does not itself
* define an annotation type.
*
* More information about annotation types can be found in section 9.6 of
* <cite>The Java™ Language Specification</cite>.
*
* The {@link java.lang.reflect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being
* non-repeatable to being repeatable.
*
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
有兴趣的读者可以动手试试,有问题或者建议可以留言,共同学习。