字节码增强技术-ASM

概述

在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.rhkb.cn/news/163175.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Error: GlobalConfigUtils setMetaData Fail Cause:java.lang.NullPointerException

文章目录 1、在开发中会出现这样的错误。2、其次&#xff0c;再看其他错误&#xff1a; 1、在开发中会出现这样的错误。 完整错误&#xff1a;Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Error: GlobalConfigUtils setMetaData Fail ! Cause…

pyflink 环境测试以及测试案例

1. py 的 环境以来采用Anaconda环境包 安装版本&#xff1a;https://www.anaconda.com/distribution/#download-section Python3.8.8版本&#xff1a;Anaconda3-2021.05-Linux-x86_64.sh 下载地址 https://repo.anaconda.com/archive/ 2. 安装 bash Anaconda3-2021.05-Linux-x…

送水订水商城小程序的作用是什么

无论瓶装水还是桶装水在市场中的需求度总是很高&#xff0c;相关送水公司或零售水企业也不少&#xff0c;消费者的购物方式一般是品牌直售或通过经销商&#xff0c;零售水则是超市/商场等场景。随着人们健康品质生活提升&#xff0c;家庭或办公等场所对桶装水或瓶装水的需求日益…

群硕与Microsoft Dynamics全球团队密切协作,加速ERP产品迭代

群硕具备强大的软件研发能力&#xff0c;搭建自动化测试平台&#xff0c;保证高质量交付。 ERP系统的引入被视为企业走向数字化转型的关键一步。 此系统有助于实现企业内部资源与外部资源的整合&#xff0c;通过软件把人、财、物、产、供、销及相应的物流、信息流、资金流、管…

找不到msvcr120.dll怎么办?msvcr120.dll丢失如何修复?

MSVCR120.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C 2012 Redistributable Package的一部分。这个文件包含了许多用于运行C应用程序的函数和类。当我们的计算机上缺少这个文件时&#xff0c;就会导致一些程序无法正常运行&#xff0c;甚至会出现系统崩溃的情…

基于当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用及论文写作、拓展分析

查看原文>>>基于当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用及论文写作、拓展分析 生态系统服务是人类从自然界中获得的直接或间接惠益&#xff0c;可分为供给服务、文化服务、调节服务和支持服务4类&#xff0c;对提升人类福…

黑豹程序员-架构师学习路线图-百科:Maven

文章目录 1、什么是maven官网下载地址 2、发展历史3、Maven的伟大发明 1、什么是maven Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and…

“智能+”时代,深维智信如何借助阿里云打造AI内容生成系统

云布道师 前言&#xff1a; 随着数字经济的发展&#xff0c;线上数字化远程销售模式越来越成为一种主流&#xff0c;销售流程也演变为线上视频会议、线下拜访等多种方式的结合。根据 Gartner 报告&#xff0c;到 2025 年 60% 的 B2B 销售组织将从基于经验和直觉的销售转变为数…

window.location对象实例详解

一、前言 Window.location 只读属性返回一个 Location 对象&#xff0c;其中包含当前标签页文档的网页地址信息。 Window.location 是一个只读 Location 对象&#xff0c;但是我们仍然可以去重新赋值更改对象值。 下面就让我们详细介绍一下location的常用属性和方法&#xf…

【Machine Learning】01-Supervised learning

01-Supervised learning 1. 机器学习入门1.1 What is Machine Learning?1.2 Supervised learning1.3 Unsupervised learning 2. Supervised learning2.1 单元线性回归模型2.1.1 Linear Regression Model&#xff08;线性回归模型&#xff09;2.1.2 Cost Function&#xff08;代…

asp.net酒店管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net酒店管理系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言开发 asp.net 酒店管理系统1 二、功能介绍 …

redhat配置本地yum源(超详细,超简单)

目录 ​编辑 1、硬件配置 2、配置本地yum源 1、硬件配置 注意这里要使用is

有什么小程序可以下载视频号的视频?

​最近有一些朋友问我&#xff0c;【视频号下载助手】和【视频下载bot】小程序&#xff0c;有什么作用&#xff1f; 首先视频号下载助手是协助用户进行下载的&#xff0c;但由于下载要符合平台规定&#xff0c;我们就将视频下载助手与视频下载bot小程序想结合的模式&#xff0…

93. 递归实现组合型枚举

题目&#xff1a; 93. 递归实现组合型枚举 - AcWing题库 思路&#xff1a; 1.从n个数中选择m个数&#xff0c;问有多少种选法。---->抽象为有m个坑位&#xff08;设置kenway[N]表示&#xff09;&#xff0c;其中填入编号为1~n的萝卜&#xff0c;问有几种填法。这里我们可…

EthernetIP 转MODBUS RTU协议网关连接FANUC机器人作为EthernetIP通信从站

远创智控YC-EIPM-RTU网关产品是一款高效的数据采集工具&#xff0c;它可以通过各种数据接口与工业领域的仪表、PLC、计量设备等产品连接&#xff0c;实时采集这些设备中的运行数据、状态数据等信息。采集到的数据经过整合和运算等操作后&#xff0c;可以被传输到其他设备或者云…

你的DOT即将解锁,请注意以下事项

作者&#xff1a; David 还记得两年前Polkadot平行链卡槽拍卖质押吗&#xff1f; 参与平行链众贷&#xff0c;质押DOT两年&#xff0c;选择投票的项目方&#xff0c;获得相应token奖励。当年质押的DOT即将解锁&#xff0c;就在十月底&#xff0c;10月24日请注意。 第一批解锁…

【TES720D】青翼科技基于复旦微的FMQL20S400全国产化ARM核心模块

板卡概述 TES720D是一款基于上海复旦微电子FMQL20S400的全国产化核心模块。该核心模块将复旦微的FMQL20S400&#xff08;兼容FMQL10S400&#xff09;的最小系统集成在了一个50*70mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;特别是用…

【数据结构】队列的实现与优化指南

一、前言 队列是一种重要的数据结构&#xff0c;它按照“先入先出”&#xff08;FIFO&#xff09;的原则管理数据。本文将介绍队列的概念、应用场景&#xff0c;以及如何使用数组实现普通队列和环形队列。 二、内容 2.1 概述 &#xff08;1&#xff09;什么是队列&#xff1…

《软件方法》2023版第1章(10)应用UML的建模工作流-大图

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 1.4 应用UML的建模工作流 1.4.1 概念 我用类图表示建模工作流相关概念如图1-16。 图1-16 建模工作流相关概念 图1-16左侧灰色部分定义了“游戏规则”&#xff0c;右侧则是在“游戏规…

30W网络对讲广播一体音柱

SV-7042T 30W网络对讲广播一体音柱 一、描述 SV-7042T是深圳锐科达电子有限公司的一款壁挂式网络有源音柱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;其采用防水设计&#xff0c;功率可以从20W到40W。SV-7042T作为网…