上接http://tianya23.blog.51cto.com/1081650/565660
上面的内容看起来很复杂,好像需要很深奥的字节码指令知识。但是在编译的类上使用ASMifierClassVisitor就可以让你知道如何用ASM生成给定的字节码。此外,在两个编译的类上(一个原始的和另一个应用特定的转换)使用然后进行比较就可以给出什么样的ASM调用应该被使用在转换器上。这个过程在几篇文章中已经详细解释了(可以参看最后的资源部分)。目前已经有了Eclipse使用的插件了,如图4,提供了从Java源生成ASM代码及比较ASMifier输出的良好支持,还包含了上下文字节码的参考。
Figure 4. Eclipse ASM plugin (Click on the picture to see a full-size image)
用ASM的访问者来跟踪类的依赖
已经有一些文章介绍了如何用ASM生成字节码。现在,我们来看一下如何用ASM分析已有的类。我们来做一个有趣的应用来获取给定的.jar文件中使用的外部类和包。简单起见,这个例子仅获取外部的依赖而不会取依赖的类型(如父类/方法参数/局部变量类型等)。仅为分析,我们不会创建那些注解/成员/方法的子访问者实例。所有使用的访问者(包括类和标识访问者)都在一个类中实现:
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;
}
为了获取依赖关系,访问者接口如ClassVisitor, AnnotationVisitor, FieldVisitor, and MethodVisitor应该选择性地集成方法的参数。几个常见的样例如下:
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);
}
}
在这个例子中,current是依赖的当前包。另一个例子是类型描述符(注解/枚举/成员类型/newarray指令的参数等);如Ljava/lang/String;, J, and [[[I。这些可以用Type.getType( desc)来获取内部格式的类名:
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;
}
}
在方法定义中的方法描述法及激活指令中的描述参数类型及返回类型。可以通过Type.getReturnType(methodDescriptor) 和Type.getArgumentTypes(methodDescriptor)来解析并取得参数和返回类型。
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
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 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);
}
}
Figure 5. Dependencies in ant.1.6.5.jar, as discovered with ASM
这个工具的全部代码会被包含在下一个ASM发布中。你可以从ASM CVS获取。
ASM1.x后的改变
如果你没有使用ASM1.x可以略过这个段。2.0中主要的结构变化是所有J2SE5.0的特性都被内建到访问者/过滤器的事件流中。因此新的API允许你用更轻便和自然的方式来处理泛型和注解。不需要显式创建注解属性实例,因为在事件流中已经包含了泛型和注解数据。例如,在1.x,ClassVisitor接口如下使用:
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)
在1.x API中,为了定义泛型信息,你必须创建SignatureAttribute的实例,而定义注解你需要RuntimeInvisibleAnnotations, RuntimeInvisibleParameterAnnotations, RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations, and AnnotationDefault的实例。然后你可以将这些实例放在相应的访问方法的attrs参数中。
在2.0,增加了新的标识参数来表示泛型信息。新的AnnotationVisitor接口被用来处理所有的注解。不再需要创建attrs集合了,而且注解数据是强类型的。然而在移植现有代码时,特别是在“适配器”类被使用时,必须注意确保所有来自适配器的方法需要重写来适应新的标识,因为编译器不用对这种情况给出警告。
ASM2.0还有些其他的改变。
1、增加了新的接口FieldVisitor 和AnnotationVisitor
2、CodeVisitor合并到MethodVisitor中了。
3、在MethodVisitor中增加了visitCode()方法简化检测首个指令。
4、Constants接口重构为Opcodes。
5、所有来自attrs包的属性被包含到ASM的事件模型中。
6、TreeClassAdapter and TreeCodeAdapter被包含到ClassNode and MethodNode中。
7、增加LabelNode类使指令集合的元素成为AbstractInsnNode的通用类型。
通常,建议使用如JDiff这样的工具来比较两个版本之间的区别。
小结
ASM2.0为开发人员屏蔽了字节码的复杂性,因而使开发人员更有效在字节码级别上使用Java的特性。这个框架不仅允许你转换和生成字节码,而且可以从现有的类中取得具体的信息。他的API继续改善,现在已经包含了J2SE5.0中的泛型和注解。接下来,还会增加Mustang(J2SE6)中的新特性。
资源
·Java Virtual Machine Specification Java虚拟机规范•·"“修订的类文件格式”(JVM规范的第4章)。包含J2SE5.0中支持的JSR-14/JSR-175/JSR-201中要求的修改及其他小的更正和调整。
·“使用ASM工具集来处理字节码”
·“使用ASM工具集来创建和读写J2SE5.0注解”
·字节码指令(BCI)。
Eugene Kuleshov是一个独立咨询师,拥有超过15年的软件设计开发经验。
版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
原文:http://www.javaworld.com/
译文:http://www.matrix.org.cn/
【注意】
1、编译之后的.class文件是经过压缩的,使用ultraedit查看,只能查看一些基本的信息,最好使用javap -c Photo进行查看
2、在运行的时候,向jvm传递参数:-javaagent:"C:/Documents and Settings/keju.wangkj/桌面/asm.project.jar"