掘金同步账号 脑袋满满的问号
在使用注解的时候,可能会听人说注解实际上是一个接口,那有没有想过为什么?今天就让我们分析一波注解实现的底层原理,深入了解注解的实现。
了解注解
首先是不是要知道什么是注解吧,在这就不描述了,可以直接google或者百度了解。
所有的注解的直接父接口都是 java.lang.annotation.Annotation,那了解一下,我们可以直接看 Annotation 接口类
image.png
上面表示:所有注解类型扩展的通用接口,还要注意这个接口本身并没有定义注解类型。
注解实现分析
既然要分析,那例子必不可少。
先让我们创建一个简单的注解 @TestAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation {
String name() default "null";
}
并使用
@TestAnnotation(name = "main")
public class Main {
public static void main(String[] args) {
TestAnnotation annotation = Main.class.getAnnotation(TestAnnotation.class);
System.out.println(annotation.name());
}
}
那怎么去查看相应注解的实现呢?
让我们断点调试,获取到annotation对象
image.png发现它实际上是一个代理对象$Proxy1。
那么这个代理对象是怎么获取的呢?
1、getAnnotation(Class<A> annotationClass),通过类型获取注解实例。
Main.class.getAnnotation(TestAnnotation.class)
2、AnnotationData annotationData(),获取注解的数据。
private Class.AnnotationData annotationData() {
while (true) { // 重试循环
Class.AnnotationData annotationData = this.annotationData;
int classRedefinedCount = this.classRedefinedCount;
if (annotationData != null &&
annotationData.redefinedCount == classRedefinedCount) {
return annotationData;
}
// null 或旧的 annotationData -> 创建新实例
Class.AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
// 对比新旧数据
if (Class.Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
// 返回新的 AnnotationData
return newAnnotationData;
}
}
}
3、createAnnotationData(int classRedefinedCount),构建注解的数据。
image.png4、AnnotationParser#parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2), 用于解析注解,这一步使用到字节码中常量池的索引解析,常量解析完毕会生成一个成员属性键值对作为下一个环节的入参,常量池的解析可以看AnnotationParser#parseMemberValue方法。
image.png5、AnnotationParser#annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1),用于生成注解的动态代理类。
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));
}
});
}
而由创建方法可知:
Proxy#newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
最终生成一个的JDK动态代理,而InvocationHandler的实例是AnnotationInvocationHandler,可以看它的成员变量、构造方法和实现InvocationHandler接口的invoke方法:
找到它,分析一波:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
// 当前注解的类型
private final Class<? extends Annotation> type;
// 注解的成员属性的名称和值的映射
private final Map<String, Object> memberValues;
/**
* 代码忽略
*/
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
// 使用 var7 给 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;
}
}
// 如果当前调用的方法是Annotation中的四个方法的话,
// AnnotationInvocationHandler 实例中已经定义好了这些方法的实现,直接调用。
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
// 自定义方法处理 从我们的注解 map 中获取这个注解属性对应的值。
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) {
// clone 返回内容
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
呐,现在我们知道了注解底层使用了JDK原生的Proxy,那么如何查看代理类的源码,这里提供两种方式来输出Proxy类的源码:
- 通过Java系统属性设置System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
- 通过-D参数指定,其实跟1差不多,参数是:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
[图片上传失败...(image-dfe842-1625205787494)]
两种方式选择一种即可。
运行后,根目录下会生成两个代理类
image.png$Proxy0 实现了 Retention 接口
$Proxy1 实现了 TestAnnotation 接口
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 {
···
}
// 静态代码块实例化方法实例,使用方法名称直接从map中取值,
// 并不是想象中使用反射获取值,而是直接调用,十分高效的处理方式。
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());
}
}
}
由上我们发现该$Proxy1代理类中的静态代码块实例化方法实例,使用方法名称直接从map中取值,并不是想象中使用反射获取值,而是直接调用,十分高效的处理方式。
小结
注解就是一个实现了Annotation接口的接口,然后在调用getAnnotation()方法的时候,返回一个代理$Proxy对象,这个是使用jdk动态代理创建,使用Proxy的newProxyInstance方法时候,传入接口 和InvocationHandler的一个实例(也就是 AnotationInvocationHandler ) ,最后返回一个代理实例,来进行值的获取与操作。
网友评论