Classworking工具箱源代码生成与字节码生成的结合资料_第1页
Classworking工具箱源代码生成与字节码生成的结合资料_第2页
Classworking工具箱源代码生成与字节码生成的结合资料_第3页
Classworking工具箱源代码生成与字节码生成的结合资料_第4页
Classworking工具箱源代码生成与字节码生成的结合资料_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、Good is good, but better carries it.精益求精,善益求善。Classworking工具箱源代码生成与字节码生成的结合-JiBX1.0采用类处理技术对类编译后生成的字节码进行了增强并且支持直接生成新类。字节码生成比工作在源代码级具有一些显著的优势,然而,有时它却在生成和调试应用程序时造成一些麻烦。即使不考虑方便的问题,一些开发者也是除了“源代码”之外什么也不信任。JiBx2.0的首席开发人员DennisSosnoski要使JiBX2.0同时支持字节码生成技术和源代码生成技术。在这篇文章中,他讨论了源代码生成技术和字节码生成技术的不同之处并且对于如何协调二者给出了

2、自己的看法。类处理技术允许程序直接处理由Java源程序编译后生成的二进制类表示。我的JiBXXML数据绑定框架就是这样的一个例子,它采用类处理技术增强了Java类文件,给类文件添加了实现与XML文件相互转换的方法。直接处理二进制类具有许多优势,包括当源文件不可用时仍然可以对类进行修改。在大多数情况下,这种二进制方法都是非常有效的。但是有时缺少源代码也有不利之处。例如:在调试过程中要使用源文件。Java调试器是被设计为工作在源代码级,如果没有与字节码指令相匹配的Java源代码,调试器实际上是无用的。如果使用基于类处理技术的框架产生错误,在追踪这些错误时,缺少源文件就成问题了。而JiBX并不从类文

3、件中删除调试信息这样您仍然可以照常调试原始代码但是不能通过添加的与XML相互转换的方法进行调试。除了调试的实用性问题外,很多开发者并不愿意信任一个框架在字节码级上处理他们的程序,并且他们自己不能方便地检查处理结果也让他们不舒服。我为JiBX2.0开发定的目标之一就是增加用源代码增强代替字节码增强的选项。在这篇文章里,我将对照地介绍处理这两种类型代码的难点和我实现时所采用的技术。同时,我也会讨论字节码操作的一些细节,这是我以前的文章中没有涉及到的,特别是在调用方法和控制流领域。源代码的替代品通常,编译器把Java源代码翻译成字节码指令序列。因此,大多数类处理库完全忽视了源代码并且只工作在字节码级

4、上。惟一的例外情况是Javassist库,它允许使用Java源代码的一种形式将字节码插入方法中或者构造新的方法(请参阅HYPERLINK/developerworks/cn/java/j-cwt10045/lresources#resources参考资料,找到我早期关于Javassist的Javaprogrammingdynamics文章)。JiBX2.0对于源代码/字节码提供双重支持的可能性,有可能建立在Javaassist的源代码处理方法上:不断地产生源代码,然后,当直接增强类文件时,Javaassist将源代码转换成字节码。但是Javassist对于源代码的支持是有限的,并且包含一些与标

5、准Java源代码不同的特性(包括方法变量的引用方式)。其次,Javassist比一些其他的字节码库(特别是ASM库,参看在文章“HYPERLINK/developerworks/java/library/j-cwt05125/index.html?S_TACT=105AGX52&S_CMP=cn-a-jClassworkingtoolkit:ASMClassworking”中的讨论)速度慢。我认为字节码增强仍然是JiBX2.0的主要目标,在某些情况下(如使用JiBX联合一种IDE自动汇编),可能需要重复进行字节码增强,所以速度也是至关重要的。最后,javassistCPL证书与JiBX的BSD

6、证书并不兼容。鉴于上述原因,我决定采用另外一种方法。我计划使用一个策略原型代替代码生成的实现,这时,根据使用源代码策略或者字节码策略的不同,同类型的操作将做不同的翻译。字节码生成与JiBX1.X的实现基本上是相同的(通过使用ASM库而不是BCEL库)。源代码生成是新功能,并且它的结构形式必须考虑到在操作层面上与字节码生成的兼容性。HYPERLINK/developerworks/cn/java/j-cwt10045/lmain#main回页首代码形式比较Java源代码通常被编译成字节码,并且一些工具甚至可以将字节码(至少是由通常的编译器产生的文件形式)反编译成源代码。这两种代码形式之间的相互转

7、换表明二者之间具有很高的兼容性。即使如此,使用源代码的编程技术与字节码的编程技术之间仍然存在实质的不同。在这一节,我将举例说明一些不同之处。方法的参数和变量Java源代码通常把方法的参数当成一个特殊形式的本地变量,参数声明直接包含在函数声明中。这个原则有一个例外,虚方法使用一个特殊的第一参数,这个参数不显示在方法的参数列表中。这个隐藏参数是指向调用这个方法的类实例的this指针。字节码对于方法参数的处理与本地变量相似。对于字节码,在方法执行的时候,每个参数占用堆栈结构的一个或者两个字。与源代码不同,字节码中的每个参数都是明确的虚方法的this指针通常在堆栈结构中的0位置,接着是方法声明中明确定

8、义的参数。不同的参数占用不同数量的堆栈结构位置,这取决于参数值类型的大小与标准字大小的比较。源代码中一般的本地变量都定义在一个块中,这个块可能是一个完整的方法体或者是一个嵌套块。在字节码中遵循同样的原则。尽管不是明确的块,字节码中定义本地变量时,也要定义一个指令范围,在这个范围内,变量是有效的。同方法变量一样,本地变量占用堆栈结构中的字。在字节码中,为了使方法所需要的堆栈空间最小化,不同位置的不同本地变量可能会使用堆栈结构中的同一个字,只要变量的有效范围不重叠。图1给出了一个简单方法的堆栈分配情况,包括本地变量。long型的值每个占用堆栈中的两个字,而int型和指针类型的值每个占用一个字。图1

9、.堆栈的使用方法调用和堆栈使用在Java源代码中,方法调用看起来非常简单:只要在方法名称后紧接着在括号里填入用逗号隔开的实参列表。实参是有位置的并且它必须与方法声明中相应的参数位置相同。如果方法返回一个结果值,则可以直接把这个值赋给一个本地变量、直接使用这个值或者不对它做任何处理。而在相应的字节码中,情况就复杂多了。方法调用之前,必须根据参数声明,按照从左到右的顺序把对应的实参压入堆栈。虚方法调用(与静态调用相对)时,调用对象实例的指针必须在其他自变量之前被压入堆栈。当所有的自变量都在堆栈中时,方法可以被调用,并且调用结束后,在堆栈中的整个自变量列表将被方法的返回值代替。为了使堆栈状态有效,字

10、节码必须考虑虚方法与静态方法调用的不同和返回值的问题。我将举例说明堆栈的使用。清单1在一个类中定义了一个方法,这个方法与HYPERLINK/developerworks/cn/java/j-cwt10045/lfigure1#figure1图1表示的相似。HYPERLINK/developerworks/cn/java/j-cwt10045/lcode2#code2清单2给出了一个字节码的注释版本,在main()方法中调用清单1中类的power()。HYPERLINK/developerworks/cn/java/j-cwt10045/lcode2#code2清单2的粗体部分表示实际的powe

11、r()方法调用的建立和返回处理。清单1.例子的源代码publicclassPowerTestprivatelongpower(longvalue,intpower)longresult;if(power5)/justcomputevalueinlineforlowloopcountresult=1;for(inti=0;ipower;i+)result*=value;else/splitthecomputationusingrecursionforspeedresult=power(value,power/2);result=result*result;if(power%2)=1)result

12、*=value;returnresult;publicstaticvoidmain(Stringargs)PowerTestinst=newPowerTest();longvalue=Long.parseLong(args0);intpower=Integer.parseInt(args1);System.out.println(value+tothepower+power+is+inst.power(value,power);清单2.添加了注释的方法调用字节码/createandinitializeclassinstance(usingdefaultconstructor)newPowerT

13、estdupinvokespecialPowerTest./storereference(duplicatedbeforeinitializercall)toinstastore_1/loadfirstcommandlineargumentvaluestringfromarrayaload_0iconst_0aaload/convertandstorevaluetovalueinvokestaticLong.parseLonglstore_2/loadsecondcommandlineargumentvaluestringfromarrayaload_0iconst_1aaload/conve

14、rtandstorevaluetopowerinvokestaticInteger.parseIntistore%4/callpower()andsaveresultvaluetoresultaload_1lload_2iload%4invokespecialPowerTest.powerlstore%5.虽然字节码的堆栈操作增加了复杂度,但是它也具有一些源代码所不具备的灵活性。例如:字节码可以处理那些不止一次被用到的值,采用在堆栈中复制它们的方式。在源代码中要获得同样的效果,则需要定义一个本地变量来保存这个值。可以构造许多操作类型以利用字节码所提供的使用堆栈的灵活性,而且JiBX1.X代码产

15、生时很大程度上就是利用这种灵活性。执行流程在字节码中控制程序执行的正常流程也比在源代码中要复杂一些。Java平台提供条件执行(使用if),三种风格的循环(for、do和while),一种开关结构(switch)。在字节码级上,只有两种不同的基本结构,一种对应于switch语句,另外一种是分支。然而,分支语句具有许多变化形式,这些变化形式足以弥补基本结构的稀少。为了演示基本分支操作,清单3显示了清单1中的power()方法。这个例子包含几个分支,三个分支语句的字节码显示为粗体。第一个分支是一个if_icmpge条件转移语句。这个分支使用堆栈顶端的两个字,从第二个字中减去第一个字并且如果减的结果为

16、非负值则转到此分支。第二个分支是无条件转移goto。它对堆栈没有影响,在任何时候都转向目标分支。第三个分支是一个if_icmpne条件转移语句。这个分支使用堆栈顶端的两个字,从第二个字从减去第一个字并且如果减的结果为非零值则转到此分支。清单3.添加了注释的具有分支的字节码/checkifpowerlessthan5iload_3iconst_5if_icmpge29/initializeresultto1andito0lconst_1lstore%4iconst_0istore%6/jumptoendifigreaterthanorequaltopower11:iload%6iload_3if

17、_icmpge59/multiplyresultvaluebyvaluelload%4lload_1lmullstore%4/incrementivalueandloopbacktotestiinc%61goto11/makerecursivecallforhalfthepower29:aload_0lload_1iload_3iconst_2idivinvokespecialPowerTest.power/squarethereturnedresultvaluelstore%4lload%4lload%4lmullstore%4/checkforoddpowervalueiload_3ico

18、nst_2iremiconst_1if_icmpne59/oddpower,multipleresultagainforfinalvaluelload%4lload_1lmullstore%4/returnresultvalue59:lload%4lreturn清单3演示了Java条件执行(if语句)和一种形式的循环(for语句)是如何被翻译成字节码的。Java源代码中的另外一种循环结构的处理方式与for相似。switch语句就复杂多了,在字节码中,有时采用正常的条件分支,有时采用两种基于表的条件分支之一。HYPERLINK/developerworks/cn/java/j-cwt10045/

19、lmain#main回页首生成机制仅仅通过提供两个完全独立的代码生成实现来支持源代码和字节码生成的合并是一种自然的方法。这种方法的确有用,但是显而易见,这将涉及很多重复的工作(在开始的时候和维护的过程中都有)。我想要避免这种重复的工作。与其重复所有的生成代码,我宁愿实现一种策略类型方式,这种方式可以同时处理这两种类型的代码。这种方式使用公用代码控制生成过程,调用策略实现方法为一种特定的操作生成合适的代码。我将用一个简单的例子阐明这种技术利用树生成代码在HYPERLINK/developerworks/cn/java/j-cwt09065/index.html上个月的专栏中,我描述了JiBX1.

20、X绑定编译器如何基于代码生成树结构产生字节码,这种树结构是根据绑定的定义构建的。采用基于树的方法处理代码生成具有如下优点:所有的代码按顺序生成不需要回退到前面生成的代码序列插入新生成的字节码。这就使实际代码生成变得相对简单。虽然JiBX2.0比JiBX1.X更直接地依赖绑定定义,但是它依然沿用了JiBX1.X使用树表示的工作原理。为了同时支持源代码和字节码生成,JiBX2.0添加了一个策略层,这个层加在由树生成的抽象操作序列和实际生成的代码之间。为了演示这个策略层是如何工作的,我将仔细讲解图2(上个月文章中的一个例子)中的JiBX1.X模型的代码生成部分,揭示字节码和源代码如何使用相同的抽象操

21、作序列。图2.代码生成模型的例子抽象操作清单4为图2的左下部分给出了实现从XML到Java对象的解编排所需的抽象操作列表。清单的顶端部分完成的是一个Customer实例的解编排,底部完成的是一个Name实例的解编排。JiBX通常将解编排的代码变成虚方法添加到类中,添加的虚方法的名称以“JiBX”开头。在清单4中,我列出了Customer解编排方法的一部分抽象操作序列和Name解编排方法的完整抽象操作序列。清单4.名称域解编排逻辑操作loadorcreateobjectfromnamefield,savetolocalcallNameunmarshallingmethodstorereferen

22、cefromlocalvariabletonamefieldunmarshalstreet1fieldvaluefromstreetelementunmarshalcityfieldvaluefromcityelement./NameunmarshallingmethodcallunmarshallingcontextmethodtopushinstanceonstackunmarshalfirstNamefieldvaluefromfirst-nameelementunmarshallastNamefieldvaluefromlast-nameelementcallunmarshalling

23、contextmethodtopopinstancefromstack生成字节码清单5显示了清单4中的抽象操作列表产生的字节码。字节码假定添加到类的解编排方法都是只有一个参数的虚方法,JiBX解编排上下文被用来解释XML输入文档。因为它们是虚方法,所以堆栈的第一个值(也就是偏移量为0的位置)填入的是对象实例的引用;堆栈的第二个值是解编排上下文的引用,后面紧接着是代码中的本地变量。清单5.名称域解编排字节码/loadorcreateobjectfromnamefield,savetolocalaload_0getfieldnamedupastore_3ifnonnull20aload_1invo

24、kestaticName.JiBX_binding1_newinstance_1_0astore_3/callNameunmarshallingmethod20:aload_3aload_1invokevirtualName.JiBX_binding1_unmarshal_1_0/storereferencefromlocalvariabletonamefieldaload_0aload_3putfieldname/unmarshalstreet1fieldvaluefromstreetelementaload_0aload_1aconst_nullldcstreetinvokevirtual

25、org.jibx.UnmarshallingContext.parseElementTextputfieldstreet1/unmarshalcityfieldvaluefromcityelementaload_0aload_1aconst_nullldccityinvokevirtualorg.jibx.UnmarshallingContext.parseElementTextputfieldcity./Name.JiBX_binding1_unmarshal_1_0method/callunmarshallingcontextmethodtopushinstanceonstackaload

26、_1aload_0invokevirtualorg.jibx.UnmarshallingContext.pushObject/unmarshalfirstNamefieldvaluefromfirst-nameelementaload_0aload_1aconst_nullldcfirst-nameinvokevirtualorg.jibx.UnmarshallingContext.parseElementTextputfieldfirstName/unmarshallastNamefieldvaluefromlast-nameelementaload_0aload_1aconst_nulll

27、dclast-nameinvokevirtualorg.jibx.UnmarshallingContext.parseElementTextputfieldlastName/callunmarshallingcontextmethodtopopinstancefromstackaload_1invokevirtualorg.jibx.UnmarshallingContext.popObjectreturn生成源代码清单6显示了抽象操作列表生成的Java源代码,这组抽象操作与生成清单5中字节码的抽象操作相同。源代码中变量名“ctx”指的是每种方法的解编排上下文参数。清单6.名称域解编排源代码/l

28、oadorcreateobjectfromnamefield,savetolocalNamelocal1=name;if(local1=null)local1=Name.JiBX_binding1_newinstance_1_0();/callNameunmarshallingmethodlocal1.JiBX_binding1_unmarshal_1_0(ctx);/storereferencefromlocalvariabletonamefieldname=local1;/unmarshalstreet1fieldvaluefromstreetelementstreet1=ctx.pars

29、eElementText(null,street);/unmarshalcityfieldvaluefromcityelementcity=ctx.parseElementText(null,city);./Name.JiBX_binding1_unmarshal_1_0method/callunmarshallingcontextmethodtopushinstanceonstackctx.pushObject(this);/unmarshalfirstNamefieldvaluefromfirst-nameelementfirstName=ctx.parseElementText(null

30、,first-name);/unmarshallastNamefieldvaluefromlast-nameelementlastName=ctx.parseElementText(null,last-name);/callunmarshallingcontextmethodtopopinstancefromstackctx.popObject();从这个简单的例子可以很容易地看出,如何从抽象操作同时实现字节码和源代码的生成。虽然支持完全灵活的JiBX数据绑定涉及到更复杂的东西,但是原理是相同的。HYPERLINK/developerworks/cn/java/j-cwt10045/lmain

31、#main回页首检查改变JiBX1.X绑定编译器检查每个绑定类,检查类中名称与实现绑定方法的模式相匹配的方法。如果绑定编译器发现名称符合的方法,它就认为在绑定编译器以前运行的时候已经添加了这些方法。当构造一个新方法时,在真正把这个新方法添加到类之前,绑定编译器首先检查是否与一个已经存在的绑定方法(在绑定编译器执行前就已经存在于类中,或者是在绑定编译器先前的执行过程中添加进来的)相匹配。如果找到相匹配的方法,则用已经存在的方法代替新构造的方法。如果类中原来存在的一个绑定方法没有被绑定编译器使用,则从类中把它删除。这种方法匹配方式不仅使JiBX添加的代码总量最小化,而且在绑定重编译的时候避免了不必

32、要的对类文件的改变。在JiBX1.X实现时,这种方法匹配方式具有一些局限性。特别是,它不能匹配相互递归调用的方法,也就是每个方法都调用其他的方法。对于JiBX2.0,我希望能够克服这种局限性。然而,在这篇文章中我只简单谈谈下面两个问题:第一,字节码方法比较通常是如何工作的;第二,我计划如何实现相应的源代码方法比较。这不是指源代码与字节码的比较对于这个问题涉及到的更多,但是幸运的是JiBX2.0的使用者并不需要关心这个问题,使用者只需要指定某个特定的类是使用字节码还是源代码修改。字节码方法比较JiBX1.X的字节码方法匹配基于对方法签名和方法的二进制字节码指令序列的比较。因为绑定编译器经常为一个绑定组件生成相同的字节码指令序列,这种匹配过程像预期的那样有效,甚至对于那些调用处于同一个类或者其他类中的其他方法的方法也同样有效,只要在调用之前检查了被调用方法的重复性。JiBX代码生成所使用的基于树的方法总能按照深度优先顺序构造方法;如果在方法调用图中没有循环(方法的相互调用),重复检查是有效的。源代码方法比较源代码方法匹配需要比字节码方法匹配多做一些工作。其中一种方式是匹配一个方法的Java语言标志(包括源代码)序列,这是与字节

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论