摘要:
java的特性如动态类加载和反射使其成为动态语言。然而在许多时候,反射是不够的,而且开发人员需要从非java源程序中生成字节码,如脚本语言groovy和beanshell,或者源数据如orm配置。当使用已经存在的类时,特别是当没有源程序时,就需要使用一些工具来做如分析类或方法的依赖性以便生成测试度量,或者来检查是否存在问题或反模式。java5中增加了一些新特性,如如注解和范型,这会影响字节码结果因而需要字节码处理工具特别注意以便保持良好的性能。本文会通过一个最小且最快的java字节码处理框架来演示。
版权声明:任何获得matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:eugene kuleshov;xmatrix(作者的blog:http://blog.matrix.org.cn/page/xmatrix)
原文:http://www.onjava.com/pub/a/onjava/2005/08/17/asm3.html
译文:http://www.matrix.org.cn/resource/article/44/44220_asm+bytecode+framework.html
关键字:asm;bytecode;framework
框架结构
asm字节码处理框架是用java开发的而且使用基于访问者模式生成字节码及驱动类到字节码的转换。这允许开发人员避免直接处理方法字节码中的类常量池及偏移,因此为开发人员隐藏了字节码的复杂性并且相对于其他类似工具如bcel, serp, or javassist提供了更好的性能。
asm分为几个包更方便灵活地构建。包结构图如图1。
figure 1. arrangement of asm packages
·core包提供了读/写/转换字节码的api而且是其他包的基础。这个包已经足够生成java字节码而且能够实现大部分的字节码转换。
·tree包提供了java字节码的内存内表示。
·analysis包为存储在来自tree包结构中的java方法字节码提供了基础的数据流分析和类型检查算法。
·commons包(asm2.0增加)提供了几个通用的字节码转换和简化字节码生成的适配器。
·util包包含几个助手类和简单的字节码较验器来方便开发和测试。
·xml包提供了与xml文件相互转换的字节码结构适配器,及兼容sax而且允许使用xslt来定义字节码转换方式的适配器。
后面几节会给出asm框架中core包的介绍。为了更好地理解这个包的组织结构,你最好有一些在jvm规范中定义的字节码结构的基础了解。下面是较高级别的类文件格式图([*]标识重复的结构)
[1]-------------------------------------------+
header and constant stack
+--------------------------------------------+
[*] class attributes
[2]------------+------------------------------+
[*] fields field name, descriptor, etc
+------------------------------+
[*] field attributes
[3]------------+------------------------------+
[*] methods method name, descriptor, etc
+------------------------------
method max stack and locals
------------------------------
[*] method code table
------------------------------
[*] method exception table
------------------------------
[*] method code attributes
+------------------------------
[*] method attributes
+-------------+------------------------------+
需要注意的一些地方:
·所有使用在类结构中的描述符,字符串和其他常量都存储在类文件开始的常量堆栈中,来自其他结构的引用是基于堆栈的序号。
·每一个类必须包含头部(包括类名,父类,接口等)和常量堆栈。而其他元素如字段列表/方法列表/属性列表都是可选的。
·每一个方法段包含相同的头信息和最大最小局部变量数的信息,这些是用来校验字节码的。对非抽象和非原生方法,还包含一个方法指令表,一个异常表及代码属性。此外,还可能有其他的方法属性。
·类的每一个属性,成员/方法/方法代码都有自己的名字,具体细节可参考jvm规范的类文件格式部分。这些属性代表字节码的各种信息,如源文件名/内部类/标识(用来存储泛型)/行号/局部变量表和注解。jvm规范也允许定义自定义的属性来包含更多的信息但标准实现的vm不会识别。注:java5注解实际上已经废弃了那些自定义属性,因为注解在主义上允许你表达更多的东西。
·方法代码表包含jvm的指令列表。一些指令(就像异常/行号/局部变量表)使用代码表中的偏移值并且所有这些偏移的值可能需要在指令从方法代码表中增删时相应调整。
如你所见,字节码转换并不容易。但是,asm框架减少了潜在的结构复杂性并且提供简化的api允许所有字节码信息的访问和复杂的转换。
基于事件的字节码处理
core包使用推方案(类似访问者模式,在sax api就使用了这种模式处理xml)来遍历复杂的字节码结构。asm定义了几个接口,如classvisitor,fieldvisitor,methodvisitor和annotationvisitor。annotationvisitor是一个特殊的接口允许你表达层次的注解结构。下面的几幅图显示这些接口是如何相互交互及配合使用实现字节码转换和从字节码获取信息。
core包逻辑上可怜分为两大部分:
1、 字节码生产者,如classreader或者按正确顺序调用了上面的访问者类的方法的自定义类。
2、 字节码消费者,如输出器(classwriter, fieldwriter, methodwriter, and annotationwriter),适配器(classadapter and methodadapter)或者其他实现了访问者接口的类。
图2给出了通用生产者/消费者交互过程的时序图。
figure 2. sequence diagram for producer-consumer interaction
在这个交互过程中,客户端应用首先创建了classreader并调用accept()方法(以classvisitor实例作为参数)。然后classreader解析类并对每一个字节码断发送“visit”事务给classvisitor。对循环的上下文,如成员/方法/注解,classvisitor可以创建继续扑克相应接口(fieldvisitor, methodvisitor, or annotationvisitor)的子访问者并返回给生产者。如果生产者接收到一个空值,他简单地忽略类的那部分(如在由访问者驱动的“延迟加载”特性时就不需要解析相应的字节码部分);否则相应的子上下文事件就传递给子访问者实例。当子上下文结束时,生产者调用visitend()方法然后移到下一部分。
字节码消费者可以通过手工传递事件给下一个链中的访问者或者使用来自传递所有访问方法给内部的访问者的classadapter/ methodadapter的访问者通过“响应链”模式连接起来。这些代理者一方面字节码的消费者方面另一方面也作为字节码的生产者。他们在实现特定的字节码转换时可以修改原始的代理方式:
1、 访问调用代理可以在删除类成员/方法/方法指令时被忽略。
2、 访问调用参数可以在重命名类/方法/类型时被修改。
3、 新访问调用可以在引入新成员/方法/注入新代码到现存代码时被增加。
classwriter访问者可以终结整个处理链,他也是最终字节码的生成者。例如:
classwriter cw = new classwriter(computemax);
classvisitor cc = new checkclassadapter(cw);
classvisitor tv =
new traceclassvisitor(cc, new printwriter(system.out));
classvisitor cv = new transformingclassadapter(tv);
classreader cr = new classreader(bytecode);
cr.accept(cv, skipdebug);
byte[] newbytecode = cw.tobytearray();
tracesignaturevisitor v =
new tracesignaturevisitor(access);
signaturereader r = new signaturereader(sign);
r.accept(v);
string genericdecl = v.getdeclaration();
string genericreturn = v.getreturntype();
string genericexceptions = v.getexceptions();
string methoddecl = genericreturn + " " +
methodname + genericdecl;
if(genericexceptions!=null) {
methoddecl += " throws " + genericexceptions;
}
public class dependencyvisitor implements
annotationvisitor, signaturevisitor,
classvisitor, fieldvisitor, methodvisitor {
...
private string getgroupkey(string name) {
int n = name.lastindexof('/');
if(n>-1) name = name.substring(0, n);
packages.add(name);
return name;
} private void addname(string name) {
if(name==null) return;
string p = getgroupkey(name);
if(current.containskey(p)) {
current.put(p, current.get(p)+1);
} else {
current.put(p, 1);
}
} private void adddesc(string desc) {
addtype(type.gettype(desc));
}
private void addtype(type t) {
switch(t.getsort()) {
case type.array:
addtype(t.getelementtype());
break;
case type.object:
addname(t.getclassname().replace('.','/'));
break;
}
} private void addmethoddesc(string desc) {
addtype(type.getreturntype(desc));
type[] types = type.getargumenttypes(desc);
for(int i = 0; i < types.length; i++) {
addtype(types[ i]);
}
} private void addsignature(string sign) {
if(sign!=null) {
new signaturereader(sign).accept(this);
}
}
private void addtypesignature(string sign) {
if(sign!=null) {
new signaturereader(sign).accepttype(this);
}
}public void visit(int version, int access,
string name, string signature,
string supername, string[] interfaces) {
string p = getgroupkey(name);
current = groups.get(p);
if(current==null) {
current = new hashmap<string,integer>();
groups.put(p, current);
}
if(signature==null) {
addname(supername);
addnames(interfaces);
} else {
addsignature(signature);
}
}
public fieldvisitor visitfield(int access,
string name, string desc,
string signature, object value) {
if(signature==null) {
adddesc(desc);
} else {
addtypesignature(signature);
}
if(value instanceof type) {
addtype((type) value);
}
return this;
}
public methodvisitor visitmethod(int access,
string name, string desc,
string signature, string[] exceptions) {
if(signature==null) {
addmethoddesc(desc);
} else {
addsignature(signature);
}
addnames(exceptions);
return this;
}
public annotationvisitor visitannotation(
string desc, boolean visible) {
adddesc(desc);
return this;
}
public annotationvisitor
visitparameterannotation(int parameter,
string desc, boolean visible) {
adddesc(desc);
return this;
}
/**
* visits a type instruction
* new, anewarray, checkcast or instanceof.
*/
public void visittypeinsn(int opcode,
string desc) {
if(desc.charat(0)=='[') {
adddesc(desc);
} else {
addname(desc);
}
}
/**
* visits a field instruction
* getstatic, putstatic, getfield or putfield.
*/
public void visitfieldinsn(int opcode,
string owner, string name, string desc) {
addname(owner);
adddesc(desc);
}
/**
* visits a method instruction invokevirtual,
* invokespecial, invokestatic or
* invokeinterface.
*/
public void visitmethodinsn(int opcode,
string owner, string name, string desc) {
addname(owner);
addmethoddesc(desc);
}
/**
* visits a ldc instruction.
*/
public void visitldcinsn(object cst) {
if(cst instanceof type) {
addtype((type) cst);
}
}
/**
* visits a multianewarray instruction.
*/
public void visitmultianewarrayinsn(
string desc, int dims) {
adddesc(desc);
}
/**
* visits a try catch block.
*/
public void visittrycatchblock(label start,
label end, label handler, string type) {
addname(type);
}
dependencyvisitor v = new dependencyvisitor();
zipfile f = new zipfile(jarname);
enumeration<? extends zipentry> en = f.entries();
while(en.hasmoreelements()) {
zipentry e = en.nextelement();
string name = e.getname();
if(name.endswith(".class")) {
classreader cr =
new classreader(f.getinputstream(e));
cr.accept(v, false);
}
}
codevisitor visitmethod(int access, string name,
string desc, string[] exceptions,
attribute attrs);
this has been split into several methods in asm 2.0:
在2.0中已经分为多个方法:
methodvisitor visitmethod(int access,
string name, string desc, string signature,
string[] exceptions)
annotationvisitor visitannotation(string desc,
boolean visible)
void visitattribute(attribute attr)
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器