莫道桑榆晚,为霞尚满天。这篇文章主要讲述Java ASM系列:(070)如何编写ASM代码相关的知识,希望能为你提供帮助。
本文属于Java ASM系列三:Tree API当中的一篇。
在ASM当中,有一个ASMifier
类,它可以打印出Core API对应的代码;但是,ASM并没有提供打印Tree API对应代码的类,因此我们就写了一个类来实现该功能。
1. PrintASMCodeTree类【Java ASM系列((070)如何编写ASM代码)】我们可以从两个方面来理解PrintASMCodeTree
类:
- 从功能上来说,
PrintASMCodeTree
类就是用来打印生成类的Tree API代码。 - 从实现上来说,
PrintASMCodeTree
类是通过调用TreePrinter
类来实现的。
public class PrintASMCodeTree {
public static void main(String[] args) throws IOException {
// (1) 设置参数
String className = "sample.HelloWorld";
int parsingOptions = ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG;
// (2) 打印结果
Printer printer = new TreePrinter();
PrintWriter printWriter = new PrintWriter(System.out, true);
TraceClassVisitor traceClassVisitor = new TraceClassVisitor(null, printer, printWriter);
new ClassReader(className).accept(traceClassVisitor, parsingOptions);
}
}
首先,我们来看一下
PrintASMCodeTree
类的功能。假如有一个HelloWorld
类,代码如下:public class HelloWorld {
public void test(int val) {
if (val == 0) {
System.out.println("val is 0");
}
else {
System.out.println("val is not 0");
}
}
}
接着,我们来看一下
TreePrinter
类,这个类是项目当中提供的一个类,继承自org.objectweb.asm.util.Printer
类。TreePrinter
类,实现比较简单,功能也非常有限,还可能存在问题(bug)。因此,对于这个类,我们可以使用它,但也应该保持一份警惕。也就是说,使用这个类生成代码之后,我们应该检查一下生成的代码是否正确。另外,还要注意区分下面五个类的作用:
ASMPrint
类:生成类的Core API代码或类的内容(功能二合一)PrintASMCodeCore
类:生成类的Core API代码(由ASMPrint
类拆分得到,功能单一)PrintASMCodeTree
类:生成类的Tree API代码PrintASMTextClass
类:查看类的内容(由ASMPrint
类拆分得到,功能单一)PrintASMTextLambda
类:查看Lambda表达式生成的匿名类的内容
org.objectweb.asm.util.Printer
类的子类。2. ControlFlowGraphRun类除了打印ASM Tree API的代码,我们也提供一个
ControlFlowGraphRun
类,可以查看方法的控制流程图:public class ControlFlowGraphRun {
public static void main(String[] args) throws Exception {
String relative_path = "sample/HelloWorld.class";
String filepath = FileUtils.getFilePath(relative_path);
byte[] bytes = FileUtils.readBytes(filepath);
//(1)构建ClassReader
ClassReader cr = new ClassReader(bytes);
//(2)生成ClassNode
ClassNode cn = new ClassNode();
int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
cr.accept(cn, parsingOptions);
//(3)查找方法
String methodName = "test";
MethodNode targetNode = null;
for (MethodNode mn : cn.methods) {
if (mn.name.equals(methodName)) {
targetNode = mn;
break;
}
}
if (targetNode == null) {
throw new RuntimeException("Can not find method: " + methodName);
}//(4)进行分析
InsnBlock[] blocks;
int kind = 2;
switch (kind) {
case 0: {
InsnText insnText = new InsnText();
List<
String>
lines = insnText.toLines(targetNode.instructions.toArray());
InsnBlock block = new InsnBlock();
block.addLines(lines);
blocks = new InsnBlock[1];
blocks[0] = block;
break;
}
case 1: {
ControlFlowEdgeAnalyzer<
BasicValue>
analyzer = new ControlFlowEdgeAnalyzer<
>
(new BasicInterpreter());
analyzer.analyze(cn.name, targetNode);
blocks = analyzer.getBlocks();
break;
}
case 2: {
ControlFlowEdgeAnalyzer<
BasicValue>
analyzer = new ControlFlowEdgeAnalyzer2<
>
(new BasicInterpreter());
analyzer.analyze(cn.name, targetNode);
blocks = analyzer.getBlocks();
break;
}
case 3: {
ControlFlowAnalyzer2 analyzer = new ControlFlowAnalyzer2();
analyzer.analyze(cn.name, targetNode);
blocks = analyzer.getBlocks();
break;
}
default: {
ControlFlowGraphAnalyzer analyzer = new ControlFlowGraphAnalyzer();
analyzer.analyze(targetNode);
blocks = analyzer.getBlocks();
}
}//(5)图形显示
InsnGraph graph = new InsnGraph(blocks);
graph.draw();
//(6)打印复杂度
CyclomaticComplexity cc = new CyclomaticComplexity();
int complexity = cc.getCyclomaticComplexity(cn.name, targetNode);
String line = String.format("%s:%s complexity: %d", targetNode.name, targetNode.desc, complexity);
System.out.println(line);
}
}
对于上面的
HelloWorld
类,可以使用javap
命令查看test
方法包含的instructions内容:$ javap -c sample.HelloWorld
Compiled from "HelloWorld.java"
public class sample.HelloWorld {
...
public void test(int);
Code:
0: iload_1
1: ifne15
4: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc#3// String val is 0
9: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;
)V
12: goto23
15: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream;
18: ldc#5// String val is not 0
20: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;
)V
23: return
}
运行
ControlFlowGraphRun
类之后,文字输出程序的复杂度:test:(I)V complexity: 2
同时,也会有图形显示,将instructions内容分成不同的子部分,并显示出子部分之间的跳转关系:
文章图片
另外,图形部分的代码位于
lsieun.graphics
包内,这些代码是从Simple Java Graphics网站复制而来的。3. 总结本文内容总结如下:
- 第一点,通过运行
PrintASMCodeTree
类,可以生成类的Tree API代码。 - 第二点,通过运行
ControlFlowGraphRun
类,可以打印方法的复杂度,也可以显示控制流程图。
推荐阅读
- postgresql 权限详解
- C语言进阶——指针进阶(字符指针指针数组数组指针)
- 聊聊VDI虚拟桌面的SID问题-前传(下)
- SSM整合配置文件详解
- 启用 Spring-Cloud-OpenFeign 配置可刷新,项目无法启动,我 TM 人傻了(上)
- OpenMLDB(一文了解带参数查询语句(paramterized query statement))
- 启用 Spring-Cloud-OpenFeign 配置可刷新,项目无法启动,我 TM 人傻了(下)
- minikube addons enable ingress 启动错误
- 线程池的各个参数的含义()