Classworking 工具箱: 深入 JiBX 代码生成内部揭示 JiBX 如何实现 XML 数据绑定的类文件增强 ![]() | ![]() |
![]() | 级别: 中级 Dennis Sosnoski (dms@sosnoski.com), Java 和 XML 顾问, Sosnoski Software Solutions Inc. 2005 年 9 月 26 日 JiBX 框架构建在类处理技术之上,用于在 Java 对象和 XML 之间进行快速而灵活的转换。但是生成正确的、经得起检验的字节码并不容易。首席开发人员 Dennis Sosnoski 在开发 1.0 产品发行版的过程中,经历了一些痛苦的类处理体验。在这篇文章中,他分享了自己的感受,讨论了用于代码生成的内部结构以及确保生成的代码符合 JVM 规则所采取的步骤。 我的 JiBX XML 数据绑定框架是在 Java 对象和 XML 文档之间进行转换的迅速而灵活的工具。大多数 XML 数据绑定框架采用的方式是从 XML 方案生成 Java 类,由框架代码实现构建到生成类中的绑定。而 JiBX 采用类处理技术增强了编译后的 Java 类文件,给类文件添加了实现绑定的方法。这种方式让 JiBX 既能处理现有的类又能处理生成的类,还提供了运行时相对较小而且操作非常迅速的好处。 与大多数使用字节码增强的框架相比,JiBX 数据绑定实现了更复杂的代码生成。在开发 JiBX 期间,我不得不应对许多挑战,好让这种复杂的代码生成可以工作。在这篇文章中,我将总结我在把 JiBX 开发到 1.0 生产发行版的过程中遇到的一些挑战和解决方案。首先,我先从介绍 JiBX 的字节码生成架构开始。 我的 JiBX 开发是在几个具体目标指导下进行的。第一个目标是:它应当支持到现有类的灵活绑定,而不一定使用生成的类。第二个目标是:它应当迅速,使用字节码增强直接把绑定代码添加到应用程序类(与侵入性较小但是较慢的反射技术相反)。第三个目标是:它不仅应当支持在单一绑定内部以不同方式使用同一个类,还应当支持到同一个类的多重绑定。随生产发行版的出现,还添加了其他目标,但是这三个初始目标已经证明是框架架构的主要影响因素。 JiBX 由两个主要组件构成:绑定编译器,它负责类实际的字节码增强;运行时,生成的代码用它对文档进行实际的编排(从对象生成 XML)和反编排(从 XML 生成对象)。运行时随着更多选项添加进来而逐渐增长,但是运行时代码的结构基本保持与项目开始的时候一样。另一方面,绑定编译器的尺寸和复杂性都增长了,字节码增强内核已经重新调整了几次结构以添加功能和提高代码质量。在 1.0 发行版中,绑定编译器的尺寸是运行时的 4 倍(228 K 比 54K),复杂性则高出许多倍。因为这个专栏关注的是类处理,所以我只讨论绑定编译器组件。 我先从一个实际的绑定示例开始。图 1 显示了我在上一期中用作 JiBX 示例的一对绑定。这两个绑定为同一 Java 类定义了不同的 XML 格式。在图中,我用颜色突出了两个绑定之间的区别(以及在两个文档中对应的区别) —— 蓝色代表 图 1. 示例绑定 ![]() 图 1 演示了绑定编译器的一些基本的灵活性(虽然表达方式非常有限)。这一对绑定提供了研究绑定编译器工作方式的良好起点。 为了处理编排和反编排,JiBX 向绑定中包含的类添加了新类和新方法。对于所有绑定到 XML 结构(与简单的文本值相对)的类,JiBX 创建了在实际实现与 XML 之间进行转换的方法。对于绑定中的顶级映射类(可以与独立文档相互转换的类),JiBX 也添加编排和反编排接口,以及这些接口定义的方法。最后,对于顶级类和其他映射类,JiBX 都生成独立的支持类,提供一个中间级别,实现了调用对应编排/反编排实现方法的接口。这种方法和类的组合看起来可能有点费解,但是这是为了支持 JiBX 绑定允许的灵活性程度所必需的。 如果把 图 1 的 Java 源代码编译成类文件,然后用 JiBX 绑定编译器编译绑定定义,那么就会得到图 2 所示的一组类和方法。在这个示例中,原始类中没有方法,所以图 2 中显示的所有方法都是 JiBX 绑定编译器添加的。 图 2.绑定之后的类图 ![]() 对于图 2 中的信息不需要在这里深入介绍,但是我要给出一个快速概述。在逻辑的最高级别(但是在图中是在中间),添加的类 第二对添加的类 详细的编排和反编排实现代码被直接添加到绑定类中。在 添加到每个类的具体方法,表现了绑定编译器的一项很出色的特性:绑定编译器并不盲目地把每个绑定的通用方法添加到绑定中使用的每个类,而是只创建实际需要的方法。如果一个方法已经添加到符合绑定需要的类中,那么会重用这个方法,而不会添加新方法。这就是为什么 图 2 中的每个类只有一个“newinstance”方法,也是为什么 在继续讨论之前,我要指出:某些类无法用直接的方法插入处理。例如,系统类是不可修改的,而用户定义接口类虽然可以修改,但是不能包含实际的实现代码。如果绑定要处理接口或不可修改的类,JiBX 转而把必要的编排/反编排代码以 为了控制上一节中描述的所有方法和类的添加过程,JiBX 绑定编译器首先用代码生成树结构的形式,创建了每个绑定的内部表示形式。这个树结构反映了编排和反编排所需要的操作嵌套。这个树的每个组件都实现了一个代码生成接口,接口定义了不同类型的代码生成使用的方法(例如 图 3 和图 4 显示了根据 图 1 中的绑定构建的代码生成树结构。两个树的上层结构是相同的,而底层结构的组织很不同,这反映了两个绑定定义的不同。 图 3. 绑定 1 的代码生成模型 ![]() 图 4. 绑定 2 的代码生成模型 ![]() 组件对代码生成方法调用的通用处理方式包括三个步骤:生成需要的设置代码、调用子组件添加子组件的代码生成、生成需要的包装代码。例如,在使用 元素包装器 组件时, 对象绑定 组件是向绑定中包含的类实际添加新方法的组件。例如,在执行 绑定编译器的早期版本不经历构造代码生成模型这一步,而是直接从绑定生成代码。添加代码生成模型是为了让字节码生成更加模块化、更容易维护。虽然这种方式有些问题(我会在下一节讨论),模型方式对这些目标来说工作得很好。
JiBX 1.0 使用 BCEL 框架对字节码进行操纵。虽然 BCEL 可以用于实现这个目的,但是,它有时使用起来要比我开始这个项目时期望的更困难。我遇到的一些问题是 BCEL 特有的,但是其他问题在其他字节码框架中也有。在这一节中,我将介绍这两类问题以及我在开发 JiBX 1.0 期间对它们的处理。 在 BCEL 框架中,我发现了几个让它使用起来很笨拙的问题。第一个是类的反射访问(在 BCEL 实现的字节码处理看起来也过于复杂,虽然有很强的灵活性,但是要操纵的对象数量也极为巨大。这些对象包括:实际的 对于 JiBX 绑定编译器,我发现最容易的方式就是把所有 BCEL 的细节隐藏在几个包装器类中。例如,用来构建新方法的类( 把 BCEL 细节隐藏在包装器中,也提供了方便地添加更多功能的位置,就像在类和方法比较代码中一样。这个比较代码定义了 在字节码生成的一般性问题方面,我列出了一个大问题:需要确保正在生成的代码符合 JVM 规范。如果生成的字节码指令序列的任何细节有误,那么把包含这个字节码的类装入 JVM 时,检验就会失败。这个失败会导致痛苦而艰难的任务:从检验失败的类返回,寻找生成错误指令的代码。 我在 JiBX 开发上花的时间,有很大一部分花在跟踪非法字节码生成问题上。正因为如此,我对代码做了多项修改,以便更容易发现这些问题。第一个修改添加了一个选项,使用包含在 我还添加了一个选项,可以打印出绑定的代码生成模型树。Justice 输出给出问题的快照。在反汇编了非法类修改后的字节代码(使用 BCEL 的 正如本.富兰克林所说,“一盎司预防顶得上一磅治疗”。当我试图从生成的代码回头去寻找问题时,我就处在疲于进行治疗的状态。我需要提前预防问题。为了做到这一点,我在方法构建器类中添加了代码,跟踪代码生成中每一点的堆栈状态,这样如果要添加的指令不符合堆栈状态,就会抛出运行时异常。从本质上说,我是在实现自己的字节码即时检验器。我发现产生的结果完全值得我付出那么多努力。 清单 1 显示的是这个即时检验产生的错误输出的示例。在这个示例中,我在处理集合的代码生成中引入了一个错误,在项目反编排循环的循环体中,向堆栈添加了一个额外项目。这个额外的项目导致堆栈状态在循环末端与开始时不匹配,所以当代码生成试图添加一个回到循环顶部的分支时,就会出现错误。错误报告包括:生成分支指令的组件(“generated by ...”这一行)和两个堆栈状态(代表 “to” 和 “from” 这两个分支位置),还有错误发生的那一点上的堆栈跟踪。 清单 1. 字节码生成的错误报告
在添加了堆栈状态跟踪之后,字节码问题的跟踪可以精确到代码的具体行。这种可确定性并不总能让修补容易一些,但是由于可以发现问题的原因,所以可以避免所有早期的问题。因为在调试方面轻松了许多,我就可以在 1.0 版发行之前向字节码生成添加一些更重要的新特性了。
对于任何想研究复杂的字节码转换实现的人来说,JiBX 1.0 字节码生成架构形成了一个有趣的案例研究。我在开发期间学到的许多教训适用于不同的字节码框架,而且预先知道缺陷可以帮您避免重蹈我在 JiBX 1.0 生产发行版的开发过程中犯的错误。 下个月,我将把 JiBX 1.0 放在一边,转移到目前针对 2.0 版 正在进行的修改。这些变化包括:对编译器代码生成进行完全重写,添加用源代码增强代替字节码增强的选项。在与字节码生成相同的框架内处理源代码生成,肯定会产生一些有趣的新问题。下个月请回来,看看我用了什么手段让这种不太可能的技术组合为下一代 JiBX 框架工作。 学习
获得产品和技术
讨论
|
联系客服