前言
对于我们JAVA开发人员来说能了解java编译的.class字节码文件,我们对于熟悉jvm就更进一层了,话不多说今天分享一下操作字节码工具 asm
环境
* jdk16
* 开发工具idea
* asm 9.7 (官网地址:https://asm.ow2.io/javadoc/index.html)
开始开发
今天分享一个方法执行时间案例来进行初步学习
包引入
pom文件引入asm
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
</dependency>
asm主要组件
asm主要组件为ClassReader、ClassWriter、ClassVisitor、MethodVIsitor
ASMifier工具
ASMifier工具能解析字节码文件并呈现出结构化,ASMifier能帮助开发者快速开发字节码是一个非常好的工具
方法运行耗时案例
新建一个测试类
新建一个测试类TestFunction 并新建一个方法eat,具体代码如下:
package org.asm;
public class TestFunction {
public void eat() throws InterruptedException {
System.out.println("我开始吃饭啦====>");
Thread.sleep(100);
System.out.println("我吃完饭啦====>");
}
}
新建一个AsmMethodVisitor 类并继承MethodVisitor ,然后重写visitCode和visitInsn方法
package org.asm;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class AsmMethodVisitor extends MethodVisitor {
protected AsmMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM9,methodVisitor);
}
@Override
public void visitCode() {
mv.visitCode();
}
@Override
public void visitInsn(int opcode) {
if (this.mv != null) {
this.mv.visitInsn(opcode);
}
}
}
新建一个main方法用来调用eat方法,并且读取TestFunction字节码文件
package org.asm;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
String fileName = "E:\\study\\untitled\\target\\classes\\org\\asm\\TestFunction.class";
byte[] bytecode= Files.readAllBytes(Paths.get(fileName));
//创建ClassReader实例
ClassReader classReader=new ClassReader(bytecode);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
AsmClassVisitor classVisitor=new AsmClassVisitor(classWriter);
//使用ClassReader的accept方法,将MyClassVisitor传递给ClassReader进行字节码解析
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);
// 将字节码保存到文件
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(classWriter.toByteArray());
fos.close();
TestFunction modifiedClass = new TestFunction();
modifiedClass.eat();
}
}
运行main方法效果如下:
使用ASMifier工具能解析字节码文件如下:
此时我们想要计算方法耗费时间则只需要添加如下代码:
package org.asm;
import java.util.Currency;
public class FuncTimeLogger {
public static long a1=0l;
public static void getStartTime() {
a1 = System.currentTimeMillis();
}
public static void timeCost() {
System.out.println("方法花费时间为:" + (System.currentTimeMillis() - a1));
}
}
package org.asm;
public class TestFunction {
public void eat() throws InterruptedException {
FuncTimeLogger.getStartTime();
System.out.println("我开始吃饭啦====>");
Thread.sleep(100);
System.out.println("我吃完饭啦====>");
FuncTimeLogger.timeCost();
}
}
运行结果如下:
再次使用ASMifier工具能解析并比对上一次执行,发现多了两行
此时我们使用asm工具进行字节码文件修改代码如下:
package org.asm;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class AsmMethodVisitor extends MethodVisitor {
protected AsmMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM9,methodVisitor);
}
@Override
public void visitCode() {
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/asm/FuncTimeLogger", "getStartTime", "()V");
}
@Override
public void visitInsn(int opcode) {
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/asm/FuncTimeLogger", "timeCost", "()V");
if (this.mv != null) {
this.mv.visitInsn(opcode);
}
}
}
执行方法去除调用耗时的方法
package org.asm;
public class TestFunction {
public void eat() throws InterruptedException {
System.out.println("我开始吃饭啦====>");
Thread.sleep(100);
System.out.println("我吃完饭啦====>");
}
}
此时我们运行一下项目看一下效果:
查看字节码文件发现字节码文件已被修改:
结语:这是一个简单的案例给大家学习一下熟悉asm,asm字节码增强有很多应用场景,大家可以更深入的学习