使用ASM直接生成字节码的方法

news2024/9/30 7:18:29

ASM是一套java字节码分析/生成/修改的工具,它能够在java程序运行时直接修改java字节码文件,换句话说它能够直接修改java的二进制文件;也能够跳过编译直接生成字节码文件。所以ASM功能非常强大,对于代码性能提升、代码问题定位都非常有帮助。lambda表达式的底层就是依靠ASM实现的,掌握这套java底层工具是成为java高级程序员的必经之路。

以下是官网对ASM的概述(ASM):

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 on performance. 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是全方位的java字节码控制和分析工具,能够在二进制文件的层面,直接修改已有的class文件或者动态生成class文件。

我这里要讲的利用ASM直接生成字节码的方法(无需经过编译步骤)

首先引入ASM的依赖:

     <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>9.2</version>
        </dependency>

然后这里有两种方法来生成java字节码:

方法一: 直接通过程序代码来生成:

如下代码:这里实际上用ASMifier.main方法来生成字节码:

package com.JvmInvokeInstructions.asm;

import org.objectweb.asm.util.ASMifier;

import java.io.IOException;

public class AsmBase {
    public void getInfo() {
        System.out.println("This is info test case");
    }

    public static void main(String[] args) throws IOException {
        // 通过ASMifier.main方法可以直接生成ASM风格的字节码
        ASMifier.main(new String[]{"com.JvmInvokeInstructions.asm.AsmBase"});
    }

}

看console的输出:

 重点看dump()方法,这个方法返回值是byte[]字节数组,这个byte[]的内容就是java文件AsmBase的字节码。换句话说如果把这个方法的返回值保存到一个文件,那么这个文件和AsmBase.class文件是完全等同的。

为了验证这个结论,我把上面输出的dump方法复制出来,然后自己写一个类加载器来加载这个dump方法返回的字节数组,从而生成类对象,然后调用该类的方法,看看输出结果。

代码如下:

public class AsmVerifyCase1 implements Opcodes {

    public static void main(String[] args) throws Exception {
        byte[] codes=dump();
        Class<?> clazz=new MyClassLoader().defineClass("com.JvmInvokeInstructions.asm.AsmBase", codes);
        clazz.getMethod("getInfo", null).invoke(clazz.newInstance(), new Object[]{});
    }

    private static class MyClassLoader extends ClassLoader implements Opcodes {
        public Class<?> defineClass(String name, byte[] b){
            return super.defineClass(name, b, 0, b.length);
        }

    }

   public static byte[] dump () throws Exception {

        ClassWriter classWriter = new ClassWriter(0);
        FieldVisitor fieldVisitor;
        RecordComponentVisitor recordComponentVisitor;
        MethodVisitor methodVisitor;
        AnnotationVisitor annotationVisitor0;

        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/JvmInvokeInstructions/asm/AsmBase", null, "java/lang/Object", null);

        classWriter.visitSource("AsmBase.java", null);

        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(6, label0);
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(RETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this", "Lcom/JvmInvokeInstructions/asm/AsmBase;", null, label0, label1, 0);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "getInfo", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(8, label0);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitLdcInsn("This is info test case");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(9, label1);
            methodVisitor.visitInsn(RETURN);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLocalVariable("this", "Lcom/JvmInvokeInstructions/asm/AsmBase;", null, label0, label2, 0);
            methodVisitor.visitMaxs(2, 1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, new String[] { "java/io/IOException" });
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(12, label0);
            methodVisitor.visitInsn(ICONST_1);
            methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/String");
            methodVisitor.visitInsn(DUP);
            methodVisitor.visitInsn(ICONST_0);
            methodVisitor.visitLdcInsn("com.JvmInvokeInstructions.asm.AsmBase");
            methodVisitor.visitInsn(AASTORE);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "org/objectweb/asm/util/ASMifier", "main", "([Ljava/lang/String;)V", false);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(13, label1);
            methodVisitor.visitInsn(RETURN);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
            methodVisitor.visitMaxs(4, 1);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();

        return classWriter.toByteArray();
    }
}

可以看到程序是可以正常运行输出的,跟写普通java程序的输出结果是一样的,只不过ASM是能直接生成.class字节码文件,无需二次编译:

 

 方法二:通过命令行来生成ASM字节码风格代码:

这里实际上通过ASM分析已有的.class文件然后生成ASM字节码风格的代码

首先需要把asm的jar包放到要分析的目标class文件所在的目录(target/classes/类的全路径):

 然后运行下面的命令:

 java -classpath "asm-util-9.2.jar;asm-9.2.jar" org.objectweb.asm.util.ASMifier AsmBase.class

 java -classpath "asm-util-9.2.jar;asm-9.2.jar" org.objectweb.asm.util.ASMifier AsmBase.class

注意classpath里面jar包的分隔符是分号;

然后会出现跟方法一同样的ASM字节码风格的代码:

 跟前面一样,将生成的代码复制出来(特别是dump方法),然后用自定义类加载器加载该方法返回的字节数组,就可以生成对应的类对象,调用类的方法,但是这个是直接生成字节码,无需二次编译。

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

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

相关文章

【技术】《Netty》从零开始学netty源码(六十)之ByteToMessageDecoder

ByteToMessageDecoder 在Netty中用于拆包的解码器都继承了抽象类ByteToMessageDecoder&#xff0c;它的类结构如下&#xff1a; 从中可以看出它其实就是一个handler&#xff0c;只要添加到pipeline中channel每次读取数据的时候都会得到解析&#xff0c;它的数据结构如下&#…

业绩涨,股价不涨,蓝思科技深陷「果链困局」

作者 | 辰纹 来源 | 洞见新研社 曾经的女首富&#xff0c;蓝思科技董事长周群飞很困惑&#xff0c;公司业绩明明还算不错&#xff0c;可股价却怎么也涨不起来&#xff0c;距离市值2000亿的顶点更是遥遥无望。 根据不久前&#xff08;4月23日&#xff09;蓝思科技发布的2022年…

Mybatis中处理特殊SQL处理逻辑

文章目录 0、前言1、模糊查询2、动态表名3、获取自增的组件4、批量删除 0、前言 在MyBatis中可能会有一些特殊的SQL需要去执行&#xff0c;一般就是模糊查询、批量删除、动态设置表名、添加功能获取自增的主键这几种&#xff0c;现在分别来进行说明。 为了方便演示 &#xff0…

强化学习路线规划之深度学习代码练习预备

前面已经练习过神经网络的相关代码&#xff0c;其实弄明白了你会发现深度学习其实是个黑盒&#xff0c;不论是TensorFlow还是pytorch都已经为我们封装好了&#xff0c;我们不需要理解深度学习如何实现&#xff0c;神经网络如何计算&#xff0c;这些都不用我们管&#xff0c;可能…

一看就懂之与栈结构(FILO)相对的——队列结构(FLFO)

文章目录 一、什么是队列&#xff0c;什么是FIFO二、使用C模拟实现以及解析队列1.结构体的定义2.队列的创建及销毁3.实现插入操作4.队列删除操作5.获取栈中有效元素个数以及头元素尾元素 源代码分享 一、什么是队列&#xff0c;什么是FIFO ​ 队列允许在一端进行插入操作&…

微服务之以nacos注册中心,以gateway路由转发服务调用实例(第一篇)

实现以nacos为注册中心,网关路由转发调用 项目版本汇总项目初始化新建仓库拉取仓库项目父工程pom初始化依赖版本选择pom文件如下 网关服务构建pom文件启动类配置文件YMLnacos启动新建命名空间配置网关yml(nacos)网关服务启动 用户服务构建pom文件启动类配置文件YML新增url接口配…

[网鼎杯 2020 青龙组]jocker 题解

32位无壳 堆栈有问题 先修堆栈在反编译 查看关键函数 对输入的字符串进行了加密 加密之后omg函数中与存储的字符串进行比较 我们先解密这个 提取数据 解密脚本 data[0x66,0x6b,0x63,0x64,0x7f,0x61,0x67,0x64,0x3b,0x56,0x6b,0x61,0x7b,0x26,0x3b,0x50,0x63,0x5f,0x4d,0x5…

javascript基础二:Javscript字符串的常用方法有哪些?

在日常开发中&#xff0c;我们对字符串也是操作蛮多&#xff0c;这里我们来整理下字符串的一下最常用的方法 一、操作方法 字符串常用的操作方法归纳为增、删、改、查 增 这里增的意思并不是说直接增添内容&#xff0c;而是创建字符串的一个副本&#xff0c;再进行操作 除了…

Python实战基础9-元组、字典、集合

一、元组 Python的元组与列表类似&#xff0c;不同职称在于元组的元素不能修改。元组使用&#xff08;&#xff09;&#xff0c;列表使用[]。 Python的元组与列表类似&#xff0c;不同之处在于元组的元素不能修改&#xff08;增删改&#xff09;&#xff0c; 元组使用小括号()…

VuePress 1.x 踩坑记录

文章目录 前言1.Node.js 版本问题2.侧边栏3.添加页面目录导航4.非首页 footer 不生效5.部署到 Github 的错误vuepress 的 docs 与 Github Pages 的 docs 目录冲突样式丢失 7.资源引用问题本地图片找不到引用 CSDN 图片报 403 错误 参考文献 前言 我的第二本开源电子书《后台开…

被问了100遍的 堆的基本功能如何实现? 绝了!!!

文章目录 堆的介绍堆的概念堆的结构 堆的向下调整算法建堆的时间复杂度 堆的向上调整算法堆的基本功能实现初始化堆打印堆堆的插入堆的删除获取堆顶的数据获取堆的数据个数堆的判空销毁堆 堆的介绍 堆的概念 堆&#xff1a;如果有一个关键码的集合K{k0,k1,k2,…,kn-1}&#x…

计算机图形学-GAMES101-9

前言 材质和光的相互作用很重要。VertexShader和FragmentShader。纹理贴图Texture mapping。 一、在三角形中插值 为什么要在三角形内部插值&#xff1f;虽然我们的操作很多是在三角形顶点上进行计算的&#xff0c;但是对于三角形内部我们也希望每个像素点能得到一个值&…

FLASH锁死,STLink烧程序烧完一次无法再烧?

ST烧程序烧完一次无法再烧&#xff0c;因为把烧录引脚占用&#xff0c;所以可以再配置一下。 &#xff08;平时不勾PA13和PA14&#xff0c;也是会通过PA13和PA14烤录&#xff0c;勾上是为了防止锁死FLASH&#xff09; 如果锁住&#xff0c;再烧烧不进去 卡点&#xff0c;按住复…

【踩坑无数终极0错版】mac-Parallels Desktop的windwos虚拟机安装最新夜神模拟器+burpsuite证书安装+app渗透

文章目录 前言一、安装夜神模拟器二、夜神模拟器配置三、安装证书与所需软件四、测试抓包总结 前言 不想说了&#xff0c;反正我吐了&#xff0c;直接看正文吧。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、安装夜神模拟器 mac上是安装不成功…

Spring6新特性来了!便捷替代Feign封装RPC接口

spring6的新特性笔者最近也有在研究&#xff0c;其中在HttpServiceProxyFactory服务代理工厂的使用方式体验上&#xff0c;笔者认为极其像是在用Feign编写RPC接口&#xff0c;使用服务代理工厂我们只要在全局配置单例的服务代里工厂bean再维护一个http interface接口就能统一的…

跨域跨网访问延迟高?中科三方云解析智能线路提供最优解析方案

在日常工作生活中&#xff0c;大多数人都是直接通过域名访问web服务器&#xff0c;但计算机并不能直接识别域名&#xff0c;因此需要域名系统&#xff08;DNS&#xff0c;Domain Name System&#xff09;将域名翻译成可由计算机直接识别的IP地址&#xff0c;这个环节就是域名解…

MOSFET开关:电源变换器基础知识及应用

​MOSFET是一种常用的场效应晶体管&#xff0c;广泛应用于电源变换器中。电源变换器是一种将输入电源转换为输出电源的电路&#xff0c;通常用于电子设备中。在本文中&#xff0c;我们将介绍MOSFET开关及其在电源变换器中的基础知识和应用。 一、MOSFET开关的基础知识 MOSFET…

MySQL---单列索引(包括普通索引、唯一索引、主键索引)、组合索引、全文索引。

1. 索引 索引是通过某种算法&#xff0c;构建出一个数据模型&#xff0c;用于快速找出在某个列中有一特定值的行&#xff0c;不使用索 引&#xff0c;MySQL必须从第一条记录开始读完整个表&#xff0c;直到找出相关的行&#xff0c;表越大&#xff0c;查询数据所花费的 时间…

算法之单调栈常见题目

什么时候需要使用单调栈&#xff1f; 通常是一维数组&#xff0c;要寻找任意一个右边或者左边第一个比自己大或小的元素的位置&#xff0c;此时我们就想到可以使用单调栈了。 单调栈的本质是空间换时间&#xff0c;因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高…

电轨车运维作业vr实操培训平台训练一批高素质的维修型人才

卡车由于使用频繁、长期载重以及各种不可预测的外界作业技能人员的培训。基于web3d开发的卡车检修作业3d模拟仿真教学系统弥补了教学条件的不足&#xff0c;在提升培训效果、节省教学经费上有显著的作用。 深圳华锐视点研发的卡车检修作业3d模拟仿真教学系统实时动态展示三维仿…