前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JDK注解原理探索

JDK注解原理探索

作者头像
孟君
发布2019-08-26 17:00:14
5410
发布2019-08-26 17:00:14
举报

在平时项目开发中,注解Annotation是我们常常用到的。比如使用Spring,其中@Autowired、@Component、@Service、@Resource、@Value、@Transactional等都是常用的;又如使用ORM,@Table、@Column等注解也是常用的;

再比如,可以使用注解去标注某个DAO的方法走哪一个数据源,如:

代码语言:javascript
复制
@ReadDataSource -  标注为走读库
@WriteDataSource - 标注为走写库

当然,如果核心链路和非核心链路走的不同的数据源,也可以通过注解标注,然后在不同数据源上进行数据的操作。

本篇文章,孟君将带着大家去看看JDK中注解的基本原理是什么。主要从如下几个方面入手:

  • 通过javap命令观察编译后的内容是什么?
  • 通过获取注解值的过程,追踪底层调用流程有哪些?
  • 小结给出注解原理的本质

一、注解编译之后是什么样子的?

编写一个名字为Permission的注解,用于测试:

代码语言:javascript
复制
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,查看一下字节码:

代码语言:javascript
复制
  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]
    )

从上述字节码信息可以看出如下几点内容:

  • Permission其实是一个接口,其继承java.lang.annotation.Annotation
代码语言:javascript
复制
public interface com.wangmengjun.tutorial.annotation.Permission 
  extends java.lang.annotation.Annotation
  • Permission中的role()变成了一个抽象方法
代码语言:javascript
复制
{
  public abstract java.lang.String role();
    descriptor: ()Ljava/lang/String;
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#10
        "NONE"
}
  • 底层使用RuntimeVisibleAnnotations说明在运行时可见的三个注解,Retention、Document和Target,这些是我们在Permission.java中指定的。
代码语言:javascript
复制
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等价于如下代码:

代码语言:javascript
复制

package com.wangmengjun.tutorial.annotation;

import java.lang.annotation.Annotation;

public interface Permission
  extends Annotation
{

  public abstract String role();
}

那么问题来了,注解对应的方法是如何赋值的呢?接下来,我们通过注解的获取来Debug一下底层调用流程。

二、Debug查看一下底层调用流程

2.1 编写一个注解测试类

代码语言:javascript
复制
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实例开始,记录调试过程,如下:

  • Class#getAnnotation(Class<A> annotationClass)
代码语言:javascript
复制
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }
  • Class#annotationData() 获取注解的数据
  • Class#createAnnotationData(int classRedefinedCount) 创建注解数据。
  • AnnotationParser#parseAnnotations,解析注解

其内部会调用parseAnnotation2来处理,方法如下:

  • AnnotationParser#parseAnnotations2
代码语言:javascript
复制
private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(
                byte[] rawAnnotations,
                ConstantPool constPool,
                Class<?> container,
                Class<? extends Annotation>[] selectAnnotationClasses)

其内部会调用另外一个不同参数的parseAnnotation2来处理,方法如下:

  • AnnotationParser#parseAnnotations2
代码语言:javascript
复制
    private static Annotation parseAnnotation2(ByteBuffer buf,
                                              ConstantPool constPool,
                                              Class<?> container,
                                              boolean exceptionOnMissingAnnotationClass,
                                              Class<? extends Annotation>[] selectAnnotationClasses)

在这个方法中,主要包含提取AnnotationType以及LinkHashMap类型的注解成员值memberValues。

获取type - AnnotationType信息获取如下:

代码语言:javascript
复制
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。

  • AnnotationParser#annotationForMap,其定义如下:
代码语言:javascript
复制
public static Annotation annotationForMap(final Class<? extends Annotation> type,
                                          final Map<String, Object> memberValues)

详细内容如下:

从上述代码可以看到,其主要作用就是生成一个动态代理,其中InvocationHandler的实例为AnnotationInvocationHandler。

  • 继续debug,发现$Proxy1.annotationType

从上述调用过程来看,注解底层也是通过动态代理来完成其方法的调用的。

那么问题又来了,$Proxy1是什么鬼,具体有些什么内容呢?

可以通过设置系统变量来保存产生的代理文件,因为我这边使用的JDK12,与JDK8等有变化。具体的参数信息如下:

  • jdk8 - sun.misc.ProxyGenerator.saveGeneratedFiles
  • jdk12 - jdk.proxy.ProxyGenerator.saveGeneratedFiles
代码语言:javascript
复制
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,查看编译后的内容,如下图所示:

使用反编译文件查看,直观的内容如下:

代码语言:javascript
复制
// 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()获取注解类型内容如下:

代码语言:javascript
复制
 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。

代码语言:javascript
复制
public final class $Proxy0 extends Proxy
  implements Retention {
 ... ...
}

三、小结

从上述两个部分,我们已经看到注解的原理基本上有两点:

一个注解定义后,其是一个继承java.lang.annotation.Annotation的接口,这就是Annotation中描述的 - The common interface extended by all annotation types。

代码语言:javascript
复制
/**
 * 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&trade; 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 {
  • 定义的方法会变成抽象方法,回忆下Permission编译后的样子。 package com.wangmengjun.tutorial.annotation; import java.lang.annotation.Annotation; public interface Permission extends Annotation { public abstract String role(); }
  • 底层是通过JDK动态代理来完成相关方法的实现的。

有兴趣的读者可以动手试试,有问题或者建议可以留言,共同学习。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 孟君的编程札记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档