工具箱源代码生成与字节码生成的结合.docx

上传人:b****2 文档编号:1242234 上传时间:2022-10-19 格式:DOCX 页数:14 大小:43.26KB
下载 相关 举报
工具箱源代码生成与字节码生成的结合.docx_第1页
第1页 / 共14页
工具箱源代码生成与字节码生成的结合.docx_第2页
第2页 / 共14页
工具箱源代码生成与字节码生成的结合.docx_第3页
第3页 / 共14页
工具箱源代码生成与字节码生成的结合.docx_第4页
第4页 / 共14页
工具箱源代码生成与字节码生成的结合.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

工具箱源代码生成与字节码生成的结合.docx

《工具箱源代码生成与字节码生成的结合.docx》由会员分享,可在线阅读,更多相关《工具箱源代码生成与字节码生成的结合.docx(14页珍藏版)》请在冰豆网上搜索。

工具箱源代码生成与字节码生成的结合.docx

工具箱源代码生成与字节码生成的结合

JiBX1.0采用类处理技术对类编译后生成的字节码进行了增强并且支持直接生成新类。

字节码生成比工作在源代码级具有一些显著的优势,然而,有时它却在生成和调试应用程序时造成一些麻烦。

即使不考虑方便的问题,一些开发者也是除了“源代码”之外什么也不信任。

JiBx2.0的首席开发人员DennisSosnoski要使JiBX2.0同时支持字节码生成技术和源代码生成技术。

在这篇文章中,他讨论了源代码生成技术和字节码生成技术的不同之处并且对于如何协调二者给出了自己的看法。

类处理技术允许程序直接处理由Java™源程序编译后生成的二进制类表示。

我的JiBXXML数据绑定框架就是这样的一个例子,它采用类处理技术增强了Java类文件,给类文件添加了实现与XML文件相互转换的方法。

直接处理二进制类具有许多优势,包括当源文件不可用时仍然可以对类进行修改。

在大多数情况下,这种二进制方法都是非常有效的。

但是有时缺少源代码也有不利之处。

例如:

在调试过程中要使用源文件。

Java调试器是被设计为工作在源代码级,如果没有与字节码指令相匹配的Java源代码,调试器实际上是无用的。

如果使用基于类处理技术的框架产生错误,在追踪这些错误时,缺少源文件就成问题了。

而JiBX并不从类文件中删除调试信息——这样您仍然可以照常调试原始代码——但是不能通过添加的与XML相互转换的方法进行调试。

除了调试的实用性问题外,很多开发者并不愿意信任一个框架在字节码级上处理他们的程序,并且他们自己不能方便地检查处理结果也让他们不舒服。

我为JiBX2.0开发定的目标之一就是增加用源代码增强代替字节码增强的选项。

在这篇文章里,我将对照地介绍处理这两种类型代码的难点和我实现时所采用的技术。

同时,我也会讨论字节码操作的一些细节,这是我以前的文章中没有涉及到的,特别是在调用方法和控制流领域。

源代码的替代品

通常,编译器把Java源代码翻译成字节码指令序列。

因此,大多数类处理库完全忽视了源代码并且只工作在字节码级上。

惟一的例外情况是Javassist库,它允许使用Java源代码的一种形式将字节码插入方法中或者构造新的方法(请参阅参考资料,找到我早期关于Javassist的Javaprogrammingdynamics文章)。

JiBX2.0对于源代码/字节码提供双重支持的可能性,有可能建立在Javaassist的源代码处理方法上:

不断地产生源代码,然后,当直接增强类文件时,Javaassist将源代码转换成字节码。

但是Javassist对于源代码的支持是有限的,并且包含一些与标准Java源代码不同的特性(包括方法变量的引用方式)。

其次,Javassist比一些其他的字节码库(特别是ASM库,参看在文章“Classworkingtoolkit:

ASMClassworking”中的讨论)速度慢。

我认为字节码增强仍然是JiBX2.0的主要目标,在某些情况下(如使用JiBX联合一种IDE自动汇编),可能需要重复进行字节码增强,所以速度也是至关重要的。

最后,javassistCPL证书与JiBX的BSD证书并不兼容。

鉴于上述原因,我决定采用另外一种方法。

我计划使用一个策略原型代替代码生成的实现,这时,根据使用源代码策略或者字节码策略的不同,同类型的操作将做不同的翻译。

字节码生成与JiBX1.X的实现基本上是相同的(通过使用ASM库而不是BCEL库)。

源代码生成是新功能,并且它的结构形式必须考虑到在操作层面上与字节码生成的兼容性。

回页首

代码形式比较

Java源代码通常被编译成字节码,并且一些工具甚至可以将字节码(至少是由通常的编译器产生的文件形式)反编译成源代码。

这两种代码形式之间的相互转换表明二者之间具有很高的兼容性。

即使如此,使用源代码的编程技术与字节码的编程技术之间仍然存在实质的不同。

在这一节,我将举例说明一些不同之处。

方法的参数和变量

Java源代码通常把方法的参数当成一个特殊形式的本地变量,参数声明直接包含在函数声明中。

这个原则有一个例外,虚方法使用一个特殊的第一参数,这个参数不显示在方法的参数列表中。

这个隐藏参数是指向调用这个方法的类实例的this指针。

字节码对于方法参数的处理与本地变量相似。

对于字节码,在方法执行的时候,每个参数占用堆栈结构的一个或者两个字。

与源代码不同,字节码中的每个参数都是明确的——虚方法的this指针通常在堆栈结构中的0位置,接着是方法声明中明确定义的参数。

不同的参数占用不同数量的堆栈结构位置,这取决于参数值类型的大小与标准字大小的比较。

源代码中一般的本地变量都定义在一个块中,这个块可能是一个完整的方法体或者是一个嵌套块。

在字节码中遵循同样的原则。

尽管不是明确的块,字节码中定义本地变量时,也要定义一个指令范围,在这个范围内,变量是有效的。

同方法变量一样,本地变量占用堆栈结构中的字。

在字节码中,为了使方法所需要的堆栈空间最小化,不同位置的不同本地变量可能会使用堆栈结构中的同一个字,只要变量的有效范围不重叠。

图1给出了一个简单方法的堆栈分配情况,包括本地变量。

long型的值每个占用堆栈中的两个字,而int型和指针类型的值每个占用一个字。

图1.堆栈的使用

方法调用和堆栈使用

在Java源代码中,方法调用看起来非常简单:

只要在方法名称后紧接着在括号里填入用逗号隔开的实参列表。

实参是有位置的并且它必须与方法声明中相应的参数位置相同。

如果方法返回一个结果值,则可以直接把这个值赋给一个本地变量、直接使用这个值或者不对它做任何处理。

而在相应的字节码中,情况就复杂多了。

方法调用之前,必须根据参数声明,按照从左到右的顺序把对应的实参压入堆栈。

虚方法调用(与静态调用相对)时,调用对象实例的指针必须在其他自变量之前被压入堆栈。

当所有的自变量都在堆栈中时,方法可以被调用,并且调用结束后,在堆栈中的整个自变量列表将被方法的返回值代替。

为了使堆栈状态有效,字节码必须考虑虚方法与静态方法调用的不同和返回值的问题。

我将举例说明堆栈的使用。

清单1在一个类中定义了一个方法,这个方法与图1表示的相似。

清单2给出了一个字节码的注释版本,在main()方法中调用清单1中类的power()。

清单2的粗体部分表示实际的power()方法调用的建立和返回处理。

清单1.例子的源代码

publicclassPowerTest

{

privatelongpower(longvalue,intpower){

longresult;

if(power<5){

//justcomputevalueinlineforlowloopcount

result=1;

for(inti=0;i

result*=value;

}

}else{

//splitthecomputationusingrecursionforspeed

result=power(value,power/2);

result=result*result;

if((power%2)==1){

result*=value;

}

}

returnresult;

}

publicstaticvoidmain(String[]args){

PowerTestinst=newPowerTest();

longvalue=Long.parseLong(args[0]);

intpower=Integer.parseInt(args[1]);

System.out.println(value+"tothepower"+

power+"is"+inst.power(value,power));

}

}

清单2.添加了注释的方法调用字节码

//createandinitializeclassinstance(usingdefaultconstructor)

newPowerTest

dup

invokespecialPowerTest.

//storereference(duplicatedbeforeinitializercall)to"inst"

astore_1

//loadfirstcommandlineargumentvaluestringfromarray

aload_0

iconst_0

aaload

//convertandstorevalueto"value"

invokestaticLong.parseLong

lstore_2

//loadsecondcommandlineargumentvaluestringfromarray

aload_0

iconst_1

aaload

//convertandstorevalueto"power"

invokestaticInteger.parseInt

istore%4

//callpower()andsaveresultvalueto"result"

aload_1

lload_2

iload%4

invokespecialPowerTest.power

lstore%5

...

虽然字节码的堆栈操作增加了复杂度,但是它也具有一些源代码所不具备的灵活性。

例如:

字节码可以处理那些不止一次被用到的值,采用在堆栈中复制它们的方式。

在源代码中要获得同样的效果,则需要定义一个本地变量来保存这个值。

可以构造许多操作类型以利用字节码所提供的使用堆栈的灵活性,而且JiBX1.X代码产生时很大程度上就是利用这种灵活性。

执行流程

在字节码中控制程序执行的正常流程也比在源代码中要复杂一些。

Java平台提供条件执行(使用if),三种风格的循环(for、do和while),一种开关结构(switch)。

在字节码级上,只有两种不同的基本结构,一种对应于switch语句,另外一种是分支。

然而,分支语句具有许多变化形式,这些变化形式足以弥补基本结构的稀少。

为了演示基本分支操作,清单3显示了清单1中的power()方法。

这个例子包含几个分支,三个分支语句的字节码显示为粗体。

第一个分支是一个if_icmpge条件转移语句。

这个分支使用堆栈顶端的两个字,从第二个字中减去第一个字并且如果减的结果为非负值则转到此分支。

第二个分支是无条件转移goto。

它对堆栈没有影响,在任何时候都转向目标分支。

第三个分支是一个if_icmpne条件转移语句。

这个分支使用堆栈顶端的两个字,从第二个字从减去第一个字并且如果减的结果为非零值则转到此分支。

清单3.添加了注释的具有分支的字节码

//checkif"power"lessthan5

iload_3

iconst_5

if_icmpge29

//initialize"result"to1and"i"to0

lconst_1

lstore%4

iconst_0

istore%6

//jumptoendif"i"greaterthanorequalto"power"

11:

iload%6

iload_3

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > IT计算机 > 互联网

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1