使用ASM 4处理Java类文件–第二部分:Tree API

什么是ASM树API: ASM树API是ASM的一部分,可让您创建/修改内存中的类。 该类被视为信息树。 像整个类一样,它是ClassNode的实例,其中包含FieldNode对象列表,MethodNode对象列表等。本文假设读者已经在这里阅读了第一部分。

通过树API的简单类:让我们使用树API创建我们的第一类。 同样,我将直接进入一个代码示例,因为没有什么比代码示例更好。 生成的类具有打印“ Hello World!”的主要方法。

TreeAPIDemo.java

package com.geekyarticles.asm;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;


public class TreeAPIDemo {
    public static void main(String [] args) throws Exception{
        
        ClassNode classNode=new ClassNode(4);//4 is just the API version number
        
        //These properties of the classNode must be set
        classNode.version=Opcodes.V1_6;//The generated class will only run on JRE 1.6 or above
        classNode.access=Opcodes.ACC_PUBLIC;
        classNode.signature="Lcom/geekyarticles/asm/Generated;";
        classNode.name="com/geekyarticles/asm/Generated";
        classNode.superName="java/lang/Object";
        
        //Create a method
        MethodNode mainMethod=new MethodNode(4,Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC,"main", "([Ljava/lang/String;)V",null, null);
        
        mainMethod.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
        mainMethod.instructions.add(new LdcInsnNode("Hello World!"));
        mainMethod.instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
        mainMethod.instructions.add(new InsnNode(Opcodes.RETURN));

        //Add the method to the classNode
        classNode.methods.add(mainMethod);
        
        //Write the class
        ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);
        classNode.accept(cw);
        
        //Dump the class in a file
        File outDir=new File("out/com/geekyarticles/asm");
        outDir.mkdirs();
        DataOutputStream dout=new DataOutputStream(new FileOutputStream(new File(outDir,"Generated.class")));
        dout.write(cw.toByteArray());
        dout.flush();
        dout.close();
        
    }
}

如您所见,代码非常简单。 与BCEL相比,它的主要优点是与BCEL不同,ASM不需要您将每个常量显式添加到常量池中。 相反,ASM会照顾常量池本身。

读取类文件: ClassNode是ClassVisitor。 因此,读取用于树API的类就像创建ClassReader对象并使用它读取类文件一样简单,同时将ClassNode对象作为其accept方法传递给参数。 完成此操作后,将通过类中存在的所有信息完全初始化传递的ClassNode。 在下面的示例中,我们将打印类中的所有方法。

TreeAPIClassReaderDemo.java

package com.geekyarticles.asm;

import java.io.FileInputStream;
import java.io.InputStream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

public class TreeAPIClassReaderDemo {

    public static void main(String[] args) throws Exception{
        InputStream in=new FileInputStream("out/com/geekyarticles/asm/Generated.class");
        
        ClassReader cr=new ClassReader(in);
        ClassNode classNode=new ClassNode();
        
        //ClassNode is a ClassVisitor
        cr.accept(classNode, 0);
        
        //Let's move through all the methods
        
        for(MethodNode methodNodeclassNode.methods){
            System.out.println(methodNode.name+"  "+methodNode.desc);
        }

    }

}

修改类文件:修改类文件是上述两个过程的组合。 我们首先以通常的方式读取该类,对数据进行必要的更改,然后将其写回到文件中。 以下程序实现了一些日志代码的自动注入。 当前,我们的Logger类仅打印到标准输出。 @Loggable注释的每个方法在开始和返回时都将被记录。 在此,我们不记录throw-exception。 但是,也可以通过检查操作码ATHROW以相同的方式实现。

LoggingInsertion.java

package com.geekyarticles.asm;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Iterator;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

public class LoggingInsertion {

    public static void main(String[] args) throws Exception{
        InputStream in=LoggingInsertion.class.getResourceAsStream("/com/geekyarticles/asm/LoggingTest.class");

        ClassReader cr=new ClassReader(in);
        ClassNode classNode=new ClassNode();
        cr.accept(classNode, 0);

        //Let's move through all the methods

        for(MethodNode methodNodeclassNode.methods){
            System.out.println(methodNode.name+"  "+methodNode.desc);
            boolean hasAnnotation=false;
            if(methodNode.visibleAnnotations!=null){
                for(AnnotationNode annotationNodemethodNode.visibleAnnotations){
                    if(annotationNode.desc.equals("Lcom/geekyarticles/asm/Loggable;")){
                        hasAnnotation=true;
                        break;
                    }
                }
            }
            if(hasAnnotation){
                //Lets insert the begin logger
                InsnList beginList=new InsnList();
                beginList.add(new LdcInsnNode(methodNode.name));
                beginList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/geekyarticles/asm/Logger", "logMethodStart", "(Ljava/lang/String;)V"));
                
                Iterator<AbstractInsnNode> insnNodes=methodNode.instructions.iterator();
                while(insnNodes.hasNext()){
                    System.out.println(insnNodes.next().getOpcode());
                }
                
                methodNode.instructions.insert(beginList);
                System.out.println(methodNode.instructions);
                
                //A method can have multiple places for return
                //All of them must be handled.
                insnNodes=methodNode.instructions.iterator();
                while(insnNodes.hasNext()){
                    AbstractInsnNode insn=insnNodes.next();
                    System.out.println(insn.getOpcode());
                    
                    if(insn.getOpcode()==Opcodes.IRETURN
                            ||insn.getOpcode()==Opcodes.RETURN
                            ||insn.getOpcode()==Opcodes.ARETURN
                            ||insn.getOpcode()==Opcodes.LRETURN
                            ||insn.getOpcode()==Opcodes.DRETURN){
                        InsnList endList=new InsnList();
                        endList.add(new LdcInsnNode(methodNode.name));
                        endList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/geekyarticles/asm/Logger", "logMethodReturn", "(Ljava/lang/String;)V"));
                        methodNode.instructions.insertBefore(insn, endList);
                    }
                    
                }
                
            }
        }

        //We are done now. so dump the class
        ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);
        classNode.accept(cw);


        File outDir=new File("out/com/geekyarticles/asm");
        outDir.mkdirs();
        DataOutputStream dout=new DataOutputStream(new FileOutputStream(new File(outDir,"LoggingTest.class")));
        dout.write(cw.toByteArray());
        dout.flush();
        dout.close();

    }

}

LoggingTest.java

package com.geekyarticles.asm;

public class LoggingTest {
    public static void run1(){
        System.out.println("run 1");
    }
    @Loggable
    public static void run2(){
        System.out.println("run 2");
    }
    
    @Loggable
    public static void main(String [] args){
        run1();
        run2();
    }
}

记录器

package com.geekyarticles.asm;

public class Logger {
    public static void logMethodStart(String methodName){
        System.out.println("Starting method: "+methodName);
    }
    
    public static void logMethodReturn(String methodName){
        System.out.println("Ending method: "+methodName);
    }
}

Loggable.java

package com.geekyarticles.asm;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {

}

如果运行此程序,则生成的文件将依赖于Logger类。 手动将Logger类复制到out目录中的正确软件包。 如果运行生成的类(它是LoggingTest类的修改版本),则将输出以下内容。

bash-4.1$ java  com.geekyarticles.asm.LoggingTest
Starting method: main
run 1
Starting method: run2
run 2
Ending method: run2
Ending method: main

请注意,与普通列表不同,在迭代InsnList对象时可以对其进行修改。 任何更改都会立即反映出来。 因此,如果在当前位置之后插入了一些指令,则也将对其进行迭代。

参考: 使用ASM 4处理Java类文件–第二部分: JCG合作伙伴提供的 Tree API  客文章博客上的Debasish Ray Chawdhuri。


翻译自: https://www.javacodegeeks.com/2012/02/manipulating-java-class-files-with-asm_22.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值