Spring 注解编程之 AnnotationMetadata

在上篇文章 Spring 注解编程之模式注解 中咱们讲到 Spring 模式注解底层原理,依靠 AnnotationMetadata 接口判断是否存在指定元注解。html

这篇文章咱们主要深刻 AnnotationMetadata,了解其底层原理。java

Spring 版本为 5.1.8-RELEASE

AnnotationMetadata 结构

使用 IDEA 生成 AnnotationMetadata 类图,以下:web

AnnotationMetadata.png

AnnotationMetadata 存在两个实现类分别为 StandardAnnotationMetadataAnnotationMetadataReadingVisitorStandardAnnotationMetadata主要使用 Java 反射原理获取元数据,而 AnnotationMetadataReadingVisitor 使用 ASM 框架获取元数据。spring

Java 反射原理你们通常比较熟悉,而 ASM 技术可能会比较陌生,下面主要篇幅介绍 AnnotationMetadataReadingVisitor 实现原理。shell

基于 AnnotationMetadata#getMetaAnnotationTypes方法,查看二者实现区别。

AnnotationMetadataReadingVisitor

ASM 是一个通用的 Java 字节码操做和分析框架。它能够用于修改现有类或直接以二进制形式动态生成类。 ASM 虽然提供与其余 Java 字节码框架如 JavassistCGLIB 相似的功能,可是其设计与实现小而快,且性能足够高。编程

Spring 直接将 ASM 框架核心源码内嵌于 Spring-core中,目前 Spring 5.1 使用 ASM 7 版本。架构

ASM 框架简单应用

Java 源代码通过编译器编译以后生成了 .class 文件。框架

Class文件是有8个字节为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,而且字节之间不存在任何空隙,对于超过8个字节的数据,将按 照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要跨平台的关键,由于 PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,所以为了Class文 件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。

Class 文件中包含类的全部信息,如接口,字段属性,方法,在内部这些信息按照必定规则紧凑排序。ASM 框会以文件流的形式读取 class 文件,而后解析过程当中使用观察者模式(Visitor),当解析器碰到相应的信息委托给观察者(Visitor)。工具

使用 ASM 框架首先须要继承 ClassVisitor,完成解析相应信息,如解析方法,字段等。post

ClassVisitor

而后使用 ClassReader 读取类文件,而后再使用 ClassReader#accpet 接受 ClassVisitor

ClassReader

输出结果为:

com/spring/learning/customizescanning/asm/Person extends java/lang/Object {

    Lcom/spring/learning/customizescanning/asm/ASMAnnotation; 
    Ljava/lang/String; name  class org.objectweb.asm.Type
    I age  class org.objectweb.asm.Type
    <init>()V
    add(II)I
    getName()Ljava/lang/String;
    setName(Ljava/lang/String;)V
    getAge()I
    setAge(I)V
}

能够看到 ClassVisitor 相应方法能够用来解析类的相关信息,这里咱们主要关注解析类上注解信息。解析注解将会在 ClassVisitor#visitAnnotation完成解析。 该方法返回了一个 AnnotationVisitor 对象,其也是一个 Visitor 对象。后续解析器会继续调用 AnnotationVisitor内部方法进行再次解析。

以上实现采用 ASM Core API ,而 ASM 框架还提供 Tree API 用法。具体用法参考: https://asm.ow2.io/

AnnotationMetadataReadingVisitor#getMetaAnnotationTypes 源码解析

AnnotationMetadataReadingVisitor#getMetaAnnotationTypes 方法实现很是简单,直接从 metaAnnotationMap 根据注解类名称获取其上面全部元注解。注解相关信息解析由 AnnotationMetadataReadingVisitor#visitAnnotation 完成。

AnnotationMetadataReadingVisitor#getMetaAnnotationTypes

visitAnnotation 方法中,metaAnnotationMap当作构造参数传入了 AnnotationAttributesReadingVisitor 对象中,metaAnnotationMap会在这里面完成赋值。

codeAnnotationAttributesReadingVisitor/code

AnnotationAttributesReadingVisitor#visitEnd 将会排除 java.lang.annotation 下的注解,而后经过递归调用 recursivelyCollectMetaAnnotations获取元注解,不断将元注解置入 metaAnnotationMap中。

AnnotationMetadataReadingVisitor#visitEnd

coderecursivelyCollectMetaAnnotations/code

最后使用 UML 时序图中,归纳以上调用流程。

AnnotationMetadataReadingVisitor5.png

Spring 4 以后版本才有递归查找元注解的方法。各位同窗能够翻阅 Spring3 的版本做为比较,能够看出 Spring 的代码功能也是逐渐迭代升级的。

StandardAnnotationMetadata

StandardAnnotationMetadata 主要使用 Java 反射原理获取相关信息。在 Spring 中封装不少了反射工具类用于操做。

StandardAnnotationMetadata#getMetaAnnotationTypes 经过使用 Spring 工具类 AnnotatedElementUtils.getMetaAnnotationTypes方法获取。源码调用比较清晰,各位同窗能够自行翻阅理解,能够参考下面时序图理解,这里再也不叙述。

StandardAnnotationMetadata4.png

总结

本文介绍了 AnnotationMetadata两种实现方案,一种基于 Java 反射,另外一种基于 ASM 框架。

两种实现方案适用于不一样场景。StandardAnnotationMetadata 基于 Java 反射,须要加载类文件。而 AnnotationMetadataReadingVisitor基于 ASM 框架无需提早加载类,因此适用于 Spring 应用扫描指定范围内模式注解时使用。

扩展阅读

  1. 实例分析JAVA CLASS的文件结构
  2. asm 官方文档
  3. 『Spring Boot 编程思想』-小马哥

其余平台.png

另外欢迎加入 Java 极客技术知识星球,获取最新 Java 技术。

相关文章
相关标签/搜索