ASM入门篇

ASM简介

ASM是一个通用的Java字节码操作和分析框架,它可以用来修改现有的类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,从中可以构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但侧重于性能。因为它的设计和实现都尽可能小和快,所以它非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)。

ASM被用在很多项目中,包括如下:

  • OpenJDK,生成lambda调用站点,以及Nashorn编译器;
  • Groovy编译器和Kotlin编译器;
  • Cobertura和Jacoco,以工具化类来度量代码覆盖率;
  • CGLIB,用于动态生成代理类;
  • Gradle,在运行时生成一些类;

更多参考官网:https://asm.ow2.io/

IDE插件

ASM是直接对字节码进行操作,如果不熟悉字节码操作集合的话,写起来会很费劲,所以ASM为主流的IDE专门提供了开发插件BytecodeOutline:

以IDEA为例,只需要对应的类中右击->Show Bytecode outline即可,大致如下图所示:

image-20210608154029529.png

面板中包含三个页签:

  • Bytecode:类对应的字节码文件;
  • ASMified:利用ASM生成字节码对应的代码;
  • Groovified:类对应的字节码指令;

ASM API

ASM库提供了两个用于生成和转换已编译类的API,一个是核心API,以基于事件的形式来表示类;另一个是树API,以基于对象的形式来表示类;可以对比XML文件解析的方式:SAX模式和DOM模式;核心API对应SAX模式,树API对应DOM模式;每种模式都有自己的优缺点:

  • 基于事件的API要快于基于对象的API,所需要的内存也较少,但在使用基于事件的API时,类转换的实现可能要更难一些;
  • 基于对象的API会把整个类加载到内存中;

ASM库组织在几个包中,这些包分布在几个单独的JAR文件中:

  • org.objectweb.asmorg.objectweb.asm.signature包:定义基于事件的API并提供类解析器和编写器组件,它们包含在asm.jar中;
  • org.objectweb.asm.util包:提供基于核心API的各种工具,这些工具可在ASM应用程序的开发和调试过程中使用,包含在asm-util.jar中;
  • org.objectweb.asm.commons包:提供了几个有用的预定义类转换器,主要基于核心API,包含在asm-commons.jar中;
  • org.objectweb.asm.tree包:定义基于对象的API,并提供用于在基于事件的表示和基于对象的表示之间进行转换的工具,包含在asm-tree.jar 中;
  • org.objectweb.asm.tree.analysis包:包提供了一个基于树API的类分析框架和几个预定义的类分析器,包含在asm-analysis.jar中;

核心API

在学习核心API之前,建议了解一下访问者模式,因为ASM对字节码的操作和分析都是基于访问者模式来实现的;

访问者模式

访问者模式建议将新行为放入一个名为访问者的独立类中, 而不是试图将其整合到已有类中。现在, 需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据;常见的应用场景:

  • 如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式;
  • 可使用访问者模式来清理辅助行为的业务逻辑;
  • 当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式;

字节码其实就是一个复杂的对象结构,还有像Sharding-Jdbc中对sql的解析也用到访问者模式,可以发现都是一些数据结构比较稳定的数据,固定的语法;

更多参考:访问者模式

访问者模式有两个核心类分别是:独立的访问者、接收访问者事件产生器;对应的ASM里面就是两个核心类:ClassVisitorClassReader,下面分别进行介绍;

ClassVisitor

用于生成和转换编译类的ASM API基于ClassVisitor抽象类,这个类中的每个方法都对应于同名的类文件结构:

public abstract class ClassVisitor {
   
    public ClassVisitor(int api);
    public ClassVisitor(int api, ClassVisitor cv);
    public void visit(int version, int access, String name,String signature, String superName, String[] interfaces);
    public void visitSource(String source, String debug);
    public void visitOuterClass(String owner, String name, String desc);
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    public void visitAttribute(Attribute attr);
    public void visitInnerClass(String name, String outerName,String innerName, int access);
    public FieldVisitor visitField(int access, String name, String desc,String signature, Object value);
    public MethodVisitor visitMethod(int access, String name, String desc,String signature, String[] exceptions);
    void visitEnd();
}

内容可以具有任意长度和复杂性的部件将通过返回辅助访问者类,主要包括:AnnotationVisitorFieldVisitorMethodVisitor;更多可以参考Java 虚拟机规范

以上所有方法都会被事件产生器ClassReader调用,所有方法中的参数都是ClassReader提供的,当然调用每个方法是有顺序的:

visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField |visitMethod )* visitEnd

首先调用visit,然后是对visitSource 的最多一个调用,接下来是对visitOuterClass 的最多一个调用 , 然后是可按任意顺序对 visitAnnotationvisitAttribute的任意多个访问 , 接下来是可按任意顺序对 visitInnerClassvisitFieldvisitMethod 的任意多个调用,最后以一个visitEnd调用结束。

ClassReader

此类主要功能就是读取字节码文件,然后把读取的数据通知ClassVisitor,字节码文件可以多种方式传入:

  • public ClassReader(final InputStream inputStream):字节流的方式;
  • public ClassReader(final String className):文件全路径;
  • public ClassReader(final byte[] classFile):二进制文件;

常见使用方式如下所示:

ClassReader classReader = new ClassReader("com/zh/asm/TestService");
ClassWriter classVisitor = new ClassWriter
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值