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

这篇文章我们主要深入 AnnotationMetadata,了解其底层原理。

Spring 版本为 5.1.8-RELEASE

AnnotationMetadata 结构

使用 IDEA 生成 AnnotationMetadata 类图,如下:

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

Java 反射原理大家一般比较熟悉,而 ASM 技术可能会比较陌生,下面主要篇幅介绍 AnnotationMetadataReadingVisitor 实现原理。

基于 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,完成解析相应信息,如解析方法,字段等。

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

输出结果为:

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 完成。

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

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

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

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

StandardAnnotationMetadata

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

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

总结

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

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

扩展阅读

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

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

Spring 注解编程之 AnnotationMetadata的更多相关文章

  1. Spring切面编程之AOP

    AOP 是OOP 的延续,是Aspect Oriented Programming 的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种 ...

  2. Spring 系列教程之 bean 的加载

    Spring 系列教程之 bean 的加载 经过前面的分析,我们终于结束了对 XML 配置文件的解析,接下来将会面临更大的挑战,就是对 bean 加载的探索.bean 加载的功能实现远比 bean 的 ...

  3. 全面分析 Spring 的编程式事务管理及声明式事务管理

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  4. C#可扩展编程之MEF学习笔记(五):MEF高级进阶

    好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...

  5. 全面分析 Spring 的编程式事务管理及声明式事务管理--转

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  6. Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。

    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...

  7. Spring的编程式事务和声明式事务

    事务管理对于企业应用来说是至关重要的,当出现异常情况时,它也可以保证数据的一致性. Spring事务管理的两种方式 spring支持编程式事务管理和声明式事务管理两种方式. 编程式事务使用Transa ...

  8. Spring注解开发之Spring常用注解

    https://blog.csdn.net/Adrian_Dai/article/details/80287557主要的注解使用: 本文用的Spring源码是4.3.16@Configuration ...

  9. Spring 注解驱动(一)基本使用规则

    Spring 注解驱动(一)基本使用规则 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 @Configur ...

随机推荐

  1. 使用Newtonsoft.Json输出JSON

    安装: Install-Package Newtonsoft.Json 代码: //序列化DataTable DataTable dt = new DataTable(); dt.Columns.Ad ...

  2. Advanced Installer 打包后,安装包在WIN10下重启后再次运行安装的解决办法

    原文:Advanced Installer 打包后,安装包在WIN10下重启后再次运行安装的解决办法 前几个月使用Advanced Installer 打包了一堆安装包,其中有使用默认主题的,也有根据 ...

  3. 批处理(bat)实现SQLServer数据库备份与还原

    原文:批处理(bat)实现SQLServer数据库备份与还原 备份数据库.bat @echo off set path=%path%;C:\Program Files (x86)\Microsoft ...

  4. 通过HTTP Header控制缓存

    我们经常通过缓存技术来加快网站的访问速度,从而提升用户体验.HTTP协议中也规定了一些和缓存相关的Header,来允许浏览器或共享高速缓存缓存资源.这些Header包括: Last-Modified ...

  5. JS获取a标签的Href 内容

    <script type="text/javascript">function getHref(obj){ alert(obj.href);} </script& ...

  6. 使用Boost的DLL库管理动态链接库

    Boost 1.61新增了一个DLL库,跟Qt中的QLibrary类似,提供了跨平台的动态库链接库加载.调用等功能.http://www.boost.org/users/history/version ...

  7. delphi中的copy函数和pos函数

    1.copy(‘csdn’,1,2) 返回的结果是 cs 注释: Copy有3个参数,第一个是要处理的字符串,第二个是要截取的开始位置,第三个是截取位数 当第三个参数大于字符长度,那么效果就是取开始位 ...

  8. 针对TianvCms的搜索优化文章源码(无版权, 随便用)

    介绍: 搜索优化虽然不是什么高深的技术, 真正实施起来却很繁琐, 后台集成搜索优化的文章可以便于便于管理, 也让新手更明白优化的步奏以及优化的日常. 特点: 根据自己的经验和查阅各种资料整理而成, 相 ...

  9. 什么是T1与E1线路

    Leased Line 租用线路 租用线路是电信公司为某一机构建造的永久性通信电路.租用线路旁路了本地交换电信局(LEC)上的交换设备,所以在每次数据传输之前无需起始阶段,它们总是连通的.如果线路是长 ...

  10. python连接数据库(2)——mongodb

    mongodb是近一段时间以来比较流行的非关系数据库之一,由于python和它都对json类型有着很好的支持,因此配合起来可谓天衣无缝. 首先要下载python对mongodb支持的包pymongo ...