字节码增强技术-ASM

news2024/11/17 21:21:13

概述

在Java中一般是用javac命令编译源代码为字节码文件,一个.java文件从编译到运行的示例如图所示:

WX20230814-215953@2x.png

使用字节码的好处:一处编译,到处运行。java 就是典型的使用字节码作为中间语言,在一个地方编译了源码,拿着.class 文件就可以在各种计算机运行。

字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。常见的字节码操作分为以下几类:

aop (1).png

优缺点如下:

字节码工具优点缺点
Java-proxy- 简单易用
- 原生支持
- 仅能代理接口或继承类
- 动态代理类需实现接口
ASM- 强大的字节码操作能力
- 高性能
- 学习曲线较陡
- 代码较复杂
AspectJ- 强大的 AOP 支持
- 高度集成
- 学习曲线较陡
- 需要特定编译器或处理器
JavaAssist- 提供高级别的 API
- 简化字节码操作
- 相对较简单
- 性能相对较低
CGLib- 可代理普通类
- 高性能
- 无法代理 final 类
- 生成代理类较大
ByteBuddy- 简洁的 API
- 高性能
- 功能全面
- 支持运行时生成和修改类
- 学习成本较高

ASM介绍

ASM是一种通用Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件。

ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or to dynamically generate classes, directly in binary form. ASM provides some common bytecode transformations and analysis algorithms from which custom complex transformations and code analysis tools can be built. ASM offers similar functionality as other Java bytecode frameworks, but is focused onperformance. Because it was designed and implemented to be as small and as fast as possible, it is well suited for use in dynamic systems (but can of course be used in a static way too, e.g. in compilers).

ASM的应用场景有AOP(Cglib就是基于ASM)、热部署、修改其他jar包中的类等。当然,涉及到如此底层的步骤,实现起来也比较麻烦。下面我们看一下ASM是如何编辑class字节码的。

ASM处理流程

目标类 class bytes -> ClassReader 解析 -> ClassVisitor 增强修改字节码 -> ClassWriter 生成增强后的 class bytes -> 通过 Instrumentation 解析加载为新的 Class。

image.png

ASM API

ASM API 提供了两种与 Java 类交互以进行转换和生成的方式:基于事件的方式和基于树的方式。

包名描述
org.objectweb.asm提供一个小巧且快速的字节码操作框架。
org.objectweb.asm.commons提供一些有用的类和方法适配器。
org.objectweb.asm.signature提供对类型签名的支持。
org.objectweb.asm.tree提供一个构造所访问的类的树表示的 ASM 访问者。
org.objectweb.asm.tree.analysis基于 asm.tree 包提供了静态代码分析的框架。
org.objectweb.asm.util提供对于编程和调试目的有用的 ASM 访问者。 基于事件的 API
核心API

ASM Core API可以类比解析XML文件中的SAX方式,不需要把这个类的整个结构读取进来,就可以用流式的方法来处理字节码文件。好处是非常节约内存,但是编程难度较大。然而出于性能考虑,一般情况下编程都使用Core API。

  • ClassReader:用于读取已经编译好的.class文件。
  • ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。
  • 各种Visitor类:如上所述,CoreAPI根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor,比如用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。为了实现AOP,重点要使用的是MethodVisitor
树形API

ASM Tree API可以类比解析XML文件中的DOM方式,把整个类的结构读取到内存中,缺点是消耗内存多,但是编程比较简单。TreeApi不同于CoreAPI,TreeAPI通过各种Node类来映射字节码的各个区域,类比DOM节点,就可以很好地理解这种编程方式。

利用ASM实现AOP

下面我们通过实践看下如何通过ASM实现增加方法,运行时修改方法:

例子

假设我们有一个类 MathUtils,其中有一个 add 方法,我们想要在方法执行前后添加日志。

public class MathUtils {
    public int add(int a, int b) {
        return a + b;
    }
}

为了利用ASM实现AOP,需要定义一个MathUtilsMethodVisitor类,用于对字节码的add方法进行修改
  
public class MathUtilsMethodAdapter extends ClassVisitor implements Opcodes {  
    public MathUtilsMethodAdapter(ClassVisitor cv) {  
        super(Opcodes.ASM4, cv);  
    }  
  
    @Override  
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {  
        if (cv != null) {  
            cv.visit(version, access, name, signature, superName, interfaces);  
        }  
    }  
  
    @Override  
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {  
  
        // 当方法名为add时进行修改
        if ("add".equals(name)) {  
            // 先得到原始的方法  
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);  
            MethodVisitor newMethod = null;  
            // 访问需要修改的方法  
            newMethod = new AsmMethodVisit(mv);  
            return newMethod;  
        }  
        if (cv != null) {  
            return cv.visitMethod(access, name, desc, signature, exceptions);  
        }  
        return null;  
    }  
}

定义AsmMethodVisit在进入方法时打印begin Entering method,返回时打印end Entering method

public class AsmMethodVisit extends MethodVisitor {  
    public AsmMethodVisit(MethodVisitor mv) {  
        super(Opcodes.ASM4, mv);  
    }  
  
    @Override  
    public void visitMethodInsn(int opcode, String owner, String name, String desc) {  
        super.visitMethodInsn(opcode, owner, name, desc);  
    }  
  
    @Override  
    public void visitCode() {  
        // 此方法在访问方法的头部时被访问到,仅被访问一次  
        // 此处可插入新的指令  
        super.visitCode();  
        mv.visitFieldInsn(org.springframework.asm.Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");  
        mv.visitLdcInsn("begin Entering method ");  
        mv.visitMethodInsn(org.springframework.asm.Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);  
    }  
  
    @Override  
    public void visitInsn(int opcode) {  
        // 此方法可以获取方法中每一条指令的操作类型,被访问多次  
        // 如应在方法结尾处添加新指令,则应判断:  
        if (opcode == Opcodes.IRETURN) {  
            // pushes the 'out' field (of type PrintStream) of the System class  
            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");  
            // pushes the "Hello World!" String constant  
            mv.visitLdcInsn("end Entering method");  
            // invokes the 'println' method (defined in the PrintStream class)  
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");  
        }  
        super.visitInsn(opcode);  
    }  
}

最后,加个测试类MathUtilsTest,使用 ASM 生成一个add1的新方法,并在运行add方法时修改字节码来增强 add 方法,实现执行前后增加日志

public class MathUtilsTest extends ClassLoader implements Opcodes {  
  
    public static void main(String args[]) throws Exception {  
        ClassReader cr = new ClassReader(MathUtils.class.getName());  
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);  
        ClassVisitor cv = new MathUtilsMethodAdapter(cw);  
        cr.accept(cv, Opcodes.ASM4);  

        // 新增加一个方法  
        MethodVisitor mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "add1", "([Ljava/lang/String;)V", null, null);  
        mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");  
        mw.visitLdcInsn("this is add method print!");  
        mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");  
        mw.visitInsn(RETURN);  
        // this code uses a maximum of two stack elements and two local  
        // variables  
        mw.visitMaxs(0, 0);  
        mw.visitEnd();  

        byte[] code = cw.toByteArray();  
        MathUtilsTest loader = new MathUtilsTest();  
        Class<?> exampleClass = loader.defineClass(MathUtils.class.getName(), code, 0, code.length);  

        for (Method method : exampleClass.getMethods()) {  
            System.out.println(method);  
        }  

        System.out.println("***************************");  
        // 调用add方法,方法执行前后打印日志  
        Object result = exampleClass.getMethods()[1].invoke(exampleClass.newInstance(), 1, 2);  
        System.out.println(result);  
        // 将生成的MathUtils.class输出到磁盘,方便我们观察
        FileOutputStream fos = new FileOutputStream("/Users/xx/MathUtils.class");  
        fos.write(code);  
        fos.close();  
    }  
}

上述程序运行结果如下:

image.png

反编译生成的MathUtils.class,可以看到如下结果:

image.png

步骤总结

利用上面这个类实现对字节码的修改。详细解读其中的代码,对字节码做修改的步骤是:

  • 首先通过MathUtilsMethodAdapter类中的visitMethod方法,判断当前字节码读到哪一个方法了。跳过构造方法 <init> 后,将需要被增强的方法交给类AsmMethodVisit来进行处理。
  • 接下来,进入类AsmMethodVisit中的visitCode方法,它会在ASM开始访问某一个方法的Code区时被调用,重写visitCode方法,将AOP中的前置逻辑就放在这里。 类AsmMethodVisit继续读取字节码指令,每当ASM访问到无参数指令时,都会调用AsmMethodVisit中的visitInsn方法。我们判断了当前指令是否为无参数的“IRETURN”指令,如果是就在它的前面添加一些指令,也就是将AOP的后置逻辑放在该方法中。
  • 综上,重写AsmMethodVisit中的两个方法,就可以实现AOP了,而重写方法时就需要用ASM的写法,手动写入或者修改字节码。通过调用methodVisitor的visitXXXXInsn()方法就可以实现字节码的插入,XXXX对应相应的操作码助记符类型,比如mv.visitLdcInsn(“end Entering method”)对应的操作码就是ldc “end”,即将字符串“end”压入栈。

ASM工具

利用ASM手写字节码时,需要利用一系列visitXXXXInsn()方法来写对应的助记符,所以需要先将每一行源代码转化为一个个的助记符,然后通过ASM的语法转换为visitXXXXInsn()这种写法。第一步将源码转化为助记符就已经够麻烦了,不熟悉字节码操作集合的话,需要我们将代码编译后再反编译,才能得到源代码对应的助记符。第二步利用ASM写字节码时,如何传参也很令人头疼。ASM社区也知道这两个问题,所以提供了工具ASM ByteCode Outline

image.png

总结

Java ASM是一个强大的 Java 字节码操作框架,用于生成、修改和分析 Java 类的字节码。它允许您在不修改源代码的情况下,通过编程方式操作字节码,从而实现动态代码生成、AOP(面向切面编程)、字节码优化、代码注入等高级功能。ASM 可以在运行时或编译时进行字节码操作,它被广泛用于许多 Java 应用和框架中,如 Spring、Hibernate 等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1110165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

逻辑回归揭秘: 从分类原理到机器学习实践

机器学习 第五课 逻辑回归 概述逻辑回归应用领域逻辑回归 vs 线性回归基本定义输出类型函数关系误差计算使用场景数据分布 逻辑回归的数学原理Sigmoid 函数多数几率似然函数逻辑回归损失函数 正则化L1 正则化L2 正则化L1 vs L2 实例 标准化为什么要标准化?如何进行标准化? 梯…

【Vue】前端解决跨域问题——反向代理

在 axios 请求路径只需填写资源路径&#xff1a; axios.get(/users).then(res > {console.log(res) })此时相当于向自己发送请求&#xff0c;会报 404。 然后在 vue.config,js 中配置反向代理&#xff1a; const { defineConfig } require(vue/cli-service) module.expo…

智安网络|从区块链到社交网络:解析去中心化的意义与应用

在当今数字化的世界中&#xff0c;一个越来越常见的概念是“去中心化”。从区块链技术到金融系统&#xff0c;从社交网络到数据存储&#xff0c;去中心化被认为是一种前所未有的方式来重新定义和改变传统的中心化结构。那么&#xff0c;去中心化到底是什么&#xff1f; 首先&a…

ThreadX任务栈大小确定及其溢出检测方法详解

在使用ThreadX实时操作系统&#xff08;RTOS&#xff09;进行嵌入式系统开发时&#xff0c;合理确定任务栈的大小及进行溢出检测是非常重要的。本篇博客将介绍如何确定ThreadX任务栈大小以及常用的溢出检测方法&#xff0c;并提供相应的代码示例。 一、确定ThreadX任务栈大小 …

基于springboot实现滴答拍摄影项目【项目源码+论文说明】计算机毕业设计

摘要 拍摄能让人放开自我、因看到不同的美景都想留下美好的记忆&#xff0c;有些人喜欢拍摄静物来表现宁静的氛围&#xff0c;通过小品类的照片&#xff0c;传达内心的情绪。而我更喜欢另一种方式&#xff0c;就是用长时间曝光把波动的海水或湖水雾化&#xff0c;拍摄出来的作…

Linux Docker图形化工具Portainer如何进行远程访问

文章目录 前言1. 部署Portainer2. 本地访问Portainer3. Linux 安装cpolar4. 配置Portainer 公网访问地址5. 公网远程访问Portainer6. 固定Portainer公网地址 前言 Portainer 是一个轻量级的容器管理工具&#xff0c;可以通过 Web 界面对 Docker 容器进行管理和监控。它提供了可…

【合宙Air700E/780E短信转发】短信转发移动联通 不要钉钉不要微信,转发自建服务器-傻瓜式搭建

官方提供的教程介绍了通过钉钉、微信等工具接收短信验证码的方法&#xff0c;但最终实现的目的是获取验证码&#xff0c;而不是通过工具间接获得。 因此&#xff0c;我们可以直接调用API接口来获取验证码&#xff0c;从而达到更快、更便捷地获得验证码的目的。 所以做了一个服…

uniapp开发微信小程序,webview内嵌h5,h5打开pdf地址,解决方案

根据公司要求&#xff0c;让我写一个h5&#xff0c;后续会嵌入到合作公司的微信小程序的webview中&#xff0c;如果是自己公司微信小程序&#xff0c;可以采取先下载下来pdf&#xff0c;然后通过wx.openDocument&#xff0c;进行单纯的预览操作&#xff0c;这个可以根据这个老哥…

chromium arm64 新版

chrome官方并没有对外公布arm64的正式版本但debian已经适配&#xff0c;以下链接提取自debian可直接一条命令安装&#xff1a;sudo dpkg -i *.deb即可 https://www.aliyundrive.com/s/vVCpzTyFjH6

CV、MV基础知识(持续补充ing)

文章目录 全局自注意机制Permutation InvariantMLP扩展比AP、mAP、FLOPs、FPS、Top1、mIoUAP (平均精度)&#xff1a;mAP (平均精度均值)&#xff1a;FLOPs (浮点运算数)&#xff1a;FPS (每秒帧数)&#xff1a;Top1 (前1精度)&#xff1a;mIoU (平均交并比)&#xff1a; Preci…

虚幻引擎:代理

一、代理类型 1.单薄代理 特点&#xff1a;允许有返回值&#xff0c;允许有参数&#xff0c;只可以一对一的传递消息就算绑定多个&#xff0c;但是总会被最后一个覆盖 2.多播代理 特点&#xff1a;不允许有返回值&#xff0c;允许有参数允许一对多传递消息 3.动态代理 …

25 个超棒的 Python 脚本合集

Python是一种功能强大且灵活的编程语言&#xff0c;拥有广泛的应用领域。下面是一个详细介绍25个超棒的Python脚本合集&#xff1a; 1. 网络爬虫&#xff1a;使用Python可以轻松编写网络爬虫&#xff0c;从网页中提取数据并保存为结构化的格式。 2. 数据清洗和预处理&#xf…

GEE:对二值图层进行腐蚀和/或膨胀操作

作者:CSDN @ _养乐多_ 腐蚀和膨胀 是数学形态学图像处理中的两个基本操作,用于修改和分析二值图像(包含只有两个像素值的图像,通常是黑和白)。这些操作可用于处理遥感图像、地理信息系统(GIS)中的栅格数据以及其他领域的图像处理。 腐蚀(Erosion):腐蚀是一种用于缩小…

北京筑龙助力中粮集团MRO集采顺利完成

近日&#xff0c;中粮集团MRO集采项目在中粮E采供应链采购平台顺利完成。作为中粮集团3年一次的集中采购类项目&#xff0c;本次采购创新采用清单式采购&#xff0c;涵盖中粮集团全集团的物资类集中采购&#xff0c;采购物料清单总量达10w&#xff0c;涉及供应商近千家&#xf…

科技成果验收测试有什么作用?需要提供哪些材料?

科技成果验收测试是指对科技成果进行系统全面的评估和检验&#xff0c;以确保其技术的稳定性、可行性和可靠性&#xff0c;以及达到预期效果和质量指标。它在新产品研发、项目推进和市场推广等方面起着重要作用。 一、科技成果验收测试的作用   1、有助于评估科技成果的可行…

人物百度百科词条创建教程分享,建议收藏

如何在互联网上树立个人品牌&#xff0c;展示自己的专业度和影响力&#xff1f;百度百科词条给大家提供了一个绝佳的展示平台。本文伯乐网络传媒将为您揭秘人物百度百科词条创建的全过程&#xff0c;助您轻松打造专属的数字名片。 一、了解百度百科词条 百度百科是全球最大的中…

vue3.0+wangEditor使用

第一步&#xff1a;安装 npm install wangeditor/editor --save第二步&#xff1a; 新建一个文件&#xff0c;命名是WangEditor.vue,代码截图如下&#xff1a; 3、第三步&#xff1a;引用 结束&#xff0c;记录一下&#xff0c;方便后续查询

一次清理全屋地面,一键清洁烘干无异味,KEEWOO启为C260 Pro洗地机上手

放长假的时候&#xff0c;我一般都会抽出两天时间好好打扫家里的卫生&#xff0c;虽说如今市面上有很多自动化的清洁工具&#xff0c;比如扫地机、擦窗机之类的&#xff0c;但是它们的清洁效果往往并不理想&#xff0c;尽管自动化程度很高&#xff0c;但是适用的地面类型有限&a…

Unity Meta Quest 开发导论:开发现状与主流 SDK

文章目录 &#x1f4d5;开发平台分类&#x1f4d5;Quest 一体机开发分类&#x1f4d5;Quest 一体机开发主流 SDK&#x1f4d5;Quest PCVR 开发主流 SDK&#x1f4d5;新手入门开发的 SDK 推荐 推荐一个高质量知识星球 XR 社区&#xff1a;SEED XR社区。包含教程答疑、及时交流、…

【GCN】Semi-Supervised Classification with Graph Convolutional Networks

GCN代码详细解读 如何运行代码详解utils.py GCN的不足[6]Reference 基于谱分解的经典方法代表之一&#xff1a;GCN, ICLR2017 Paper Code(pytorch) 如何运行 先看整个代码文件的结构&#xff08;运行过的&#xff09;&#xff1a; 运行的话&#xff1a; 首先一键安装需要的库…