引言
动机
程序分析、程序生成和程序转换都是非常有用的技术,可在许多应用环境下使用:
- 程序分析,既可能只是简单的语法分析(syntaxic parsing),也可能是完整的语义分析(sematic analysis),可用于查找应用程序中的潜在 bug、检测未被用到的代码、对代码 实施逆向工程,等等。
- 程序生成,在编译器中使用。这些编译器不仅包括传统编译器,还包括用于分布式程序 设计的 stub 编译器或 skeleton 编译器,以及 JIT(即时)编译器,等等。
- 程序转换可,用于优化或混淆(obfuscate)程序、向应用程序中插入调试或性能监视代 码,用于面向方面的程序设计,等等。
所有这些技术都可针对任意程序设计语言使用,但对于不同语言,其使用的难易程度可能会 有所不同。对于 Java 语言,它们可用于 Java 源代码或编译后的 Java 类。在使用经过编译的类时,其好处之一显然就是不需要源代码。因此,程序转换可用于任何应用程序,既包括保密的源代码,也包含商业应用程序。使用已编译类的另一个好处是,有可能在运行时,在马上就要将类加载到Java 虚拟机之前,对类进行分析、生成或转换(在运行时生成和编译源代码也可以,但其速度很慢,而且需要一个完整的 Java 编译器)。其好处是,诸如 stub 编译器或方面编织器等工具对用户变为透明。
由于程序分析、生成和转换技术的用途众多,所以人们针对许多语言实现了许多用于分析、 生成和转换程序的工具,这些语言中就包括 Java 在内。ASM 就是为 Java 语言设计的工具之一,用于进行运行时(也是脱机的)类生成与转换。
于是,人们设计了 ASM 库,用于处理经过编译的 Java 类。这个库的设计使其尽可能保持快速和小型化。对于那些在运行时使用 ASM 进行动态类生成或转换的应用程序来说,尽可能提高库的运行速度是非常重要的,这样可以保证这些应用程序的速度不致下降过多。而保持 ASM 库的小型化也非常重要,一方面是为了在内存有限的环境中使用,另一方面,也为了避免使那些使用 ASM 的小型应用程序或库增大过多。
概述
范围
ASM 库的目的是生成、转换和分析以字节数组表示的已编译 Java 类(它们在磁盘中的存储 和在 Java 虚拟机中的加载都采用这种字节数组形式)。为此,ASM 提供了一些工具,使用高于字节级别的概念来读写和转换这种字节数组,这些概念包括数值常数、字符串、Java 标识符、Java类型、Java 类结构元素,等等。注意,ASM 库的范围严格限制于类的读、写、转换和分析。具体来说,类的加载过程就超出了它的范围之外。
模型
ASM 库提供了两个用于生成和转换已编译类的 API,一个是Core API,以基于事件的形式 来表示类,另一个是Tree API,以基于对象的形式来表示类。
在采用基于事件的模型时,类是用一系列事件来表示的,每个事件表示类的一个元素,比如它的一个标头、一个字段、一个方法声明、一条指令,等等。基于事件的 API 定义了一组可能事件,以及这些事件必须遵循的发生顺序,还提供了一个类分析器,为每个被分析元素生成一个 事件,还提供一个类写入器,由这些事件的序列生成经过编译的类。
而在采用基于对象的模型时,类用一个对象树表示,每个对象表示类的一部分,比如类本身、 一个字段、一个方法、一条指令,等等,每个对象都有一些引用,指向表示其组成部分的对象。 基于对象的 API 提供了一种方法,可以将表示一个类的事件序列转换为表示同一个类的对象树,也可以反过来,将对象树表示为等价的事件序列。换言之,基于对象的 API 构建在基于事件的 API 之上。
这两个 API 可以与“用于 XML 的简单 API”(Simple API for XML,SAX)和用于 XML 文档的“文档对象模型(Document Object Model,DOM)API”相比较:基于事件的 API 类似于SAX,而基于对象的 API 类似于 DOM。基于对象的 API 构建在基于事件的 API 之上,类似于 DOM 可在 SAX 的上层提供。
ASM 之所以要提供两个 API,是因为没有哪种 API 是最佳的。实际上,每个 API 都有自己 的优缺点:
- 基于事件的 API 要快于基于对象的 API,所需要的内存也较少,因为它不需要在内存中 创建和存储用于表示类的对象树(SAX 与 DOM 之间也有同样的差异)。
- 但在使用基于事件的 API 时,类转换的实现可能要更难一些,因为在任意给定时刻, 类中只有一个元素可供使用(也就是与当前事件对应的元素),而在使用基于对象的 API 时,可以在内存中获得整个类。
注意,这两个 API 都是仅能同时维护一个类,而且独立于其他类,也就是说,它们不会维 护有关类层级结构的信息,如果类的转换影响到其他类,那其他这些类的修改应当由用户负责完 成。
体系结构
ASM 应用程序拥有一个很强壮的体系结构方面(aspect)。事实上,对于基于事件的 API, 其组织结构是围绕事件生成器(类分析器)、事件使用器(类写入器)和各种预定义的事件筛选 器进行的,在这一结构中可以添加用户定义的生成器、使用器和筛选器。因此,这一 API 的使 用分为两个步骤:
- 将事件生成器、筛选器和使用器组件组装为可能很复杂的体系结构,
- 然后启动事件生成器,以执行生成或转换过程。
基于对象的 API 也有一个体系结构方面:实际上,用于操作类树的类生成器或转换器组件 是可以组成形成的,它们之间的链接代表着转换的顺序。
尽管典型 ASM 应用程序中的大多数组件体系结构都非常简单,但还是可以想象一下类似于 如下所示的复杂体系结构,其中的箭头表示在类分析器、写入器或转换器之间进行的基于事件或 基于对象的通信,在整个链中的任何位置,都可能会在基于事件与基于对象的表示之间进行转换
组织形式
ASM 库划分为几个包,以几个 jar 文件的形式进行分发:
- org.objectweb.asm 和 org.objectweb.asm.signature 包定义了基于事件的 API,并提供了类分析器和写入器组件。它们包含在 asm.jar 存档文件中。
- org.objectweb.asm.util 包,位于 asm-util.jar 存档文件中,提供各种基于 核心 API 的工具,可以在开发和调试 ASM 应用程序时使用。
- org.objectweb.asm.commons 包提供了几个很有用的预定义类转换器,它们大多 是基于核心 API 的。这个包包含在 asm-commons.jar 存档文件中。
- org.objectweb.asm.tree 包,位于 asm-tree.jar 存档文件中,定义了基于对 象的 API,并提供了一些工具,用于在基于事件和基于对象的表示方法之间进行转换。
- org.objectweb.asm.tree.analysis 包提供了一个类分析框架和几个预定义的 类分析器,它们以树 API 为基础。这个包包含在 asm-analysis.jar 存档文件中。