ASM字节码插桩技术初探

news2024/12/22 15:05:26

一、ASM简介

     ASM(全称:ASMifier Class Visitor)是一个java字节码操纵框架,ASM 提供了许多 API 和工具,可以直接以二进制形式读取和修改类文件、动态生成类或者增强既有类的功能。                

1、 ASM 主要作用

       asm用于生成、编辑、分析java的class文件

◆ 字节码生成

         可以通过 ASM 生成 Java 类的字节码,可以用于生成代理类、动态生成类等场景。

◆ 字节码修改

         可以通过 ASM 对已有的类字节码进行修改,实现一些类增强、方法拦截等功能。

◆ 字节码分析

       可以通过 ASM 对已有的类字节码进行分析,实现一些类结构的分析和转换。

2、核心API

   ASM框架中的核心类有以下几个:

◆ ClassReader

        该类用来解析编译过的class字节码文件。

◆ ClassWriter

       该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字

       节码文件。

◆ ClassAdapter

      该类也实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。

3、工作原理

        要了解Asm的工作原理,首先需要对java的字节码文件有一定了解。

◆ class文件结构

        一个Class文件都对应着唯一的一个类或接口的定义信息,Class 文件是一组以 8 个字节为基础单位的二进制流。calss文件由“无符号数” 和 “表” 两种数据类型组成,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。在 Java 类文件中包含了许多大小不同的项,由于每一项的结构都有严格规定,这使得 class 文件能够从头到尾被顺利地解析。

       无符号数属于基本数据类型,以 u1、u2、u4、u8 来分别代表  个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值;
        表是由多个无符号数或其他表作为数据项构成的复合数据类型,以“_info”结尾。表用于描述有层次关系的复合结构的数据。

       主要的字节码数据类型见3-1图表格。

                                   (图3-1)
◆ ASM运行机制

       基于上述字节码的结构特点,ASM的ClassReader这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码,对字节码树进行遍历,在遍历过程中对字节码进行修改。

   ClassReader的accept会动态绑定ClassVisitor的实现类,依次调用接口的方法,字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,ClassReader知道如何调用各种 visit 函数。

   ClassAdapter类实现了 ClassVisitor接口所定义的所有函数,当新建一个 ClassAdaptor对象的时候,需要传入一个实现了 ClassVisitor接口的对象,作为职责链中的下一个访问者Visitor,这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从ClassAdaptor类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。

二、代码实操

1、生成字节码

            现在我们通过asm生成一个Student类,类定义一个name属性及其getter setter方法


    public class Student{

        private String   name;

        private int  age;

        public void setName(String  name) {
            this.name= name;
        }

        public String getName() {
            return name;
        }

    }

             asm 生成类字节码

        ClassWriter classWriter = new ClassWriter(0);
        /// 定义类名Student  包名使用/分隔
        classWriter.visit(61, ACC_PUBLIC | ACC_SUPER, "org/example/cn/entity/Student", null, "java/lang/Object", null);
        classWriter.visitSource("Student.java", null);
        /// 定义name属性
        FieldVisitor   fieldVisitor = classWriter.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);
        fieldVisitor.visitEnd();
        /// 定义age属性
        fieldVisitor = classWriter.visitField(ACC_PRIVATE, "age", "I", null, null);
        fieldVisitor.visitEnd();

        /// 定义空参构造器
        MethodVisitor  methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        methodVisitor.visitCode();
        Label initLabelStart = new Label();
        methodVisitor.visitLabel(initLabelStart);
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V" );
        methodVisitor.visitInsn(RETURN);
        Label initLabelEnd = new Label();
        methodVisitor.visitLabel(initLabelEnd);
        methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, initLabelStart, initLabelEnd, 0);
        methodVisitor.visitMaxs(1, 1);
        methodVisitor.visitEnd();

        /// 定义setName方法
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);
        methodVisitor.visitCode();
        Label setNameLabel0 = new Label();
        methodVisitor.visitLabel(setNameLabel0);
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitVarInsn(ALOAD, 1);
        methodVisitor.visitFieldInsn(PUTFIELD, "org/example/cn/entity/Student", "name", "Ljava/lang/String;");
        Label setNameLabel1 = new Label();
        methodVisitor.visitLabel(setNameLabel1);
        methodVisitor.visitInsn(RETURN);
        Label setNameLabel2 = new Label();
        methodVisitor.visitLabel(setNameLabel2);
        methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, setNameLabel0, setNameLabel2, 0);
        methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, setNameLabel0, setNameLabel2, 1);
        methodVisitor.visitMaxs(2, 2);
        methodVisitor.visitEnd();

        /// 定义getName方法
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);
        methodVisitor.visitCode();
        Label getNameLabel0 = new Label();
        methodVisitor.visitLabel(getNameLabel0);
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitFieldInsn(GETFIELD, "org/example/cn/entity/Student", "name", "Ljava/lang/String;");
        methodVisitor.visitInsn(ARETURN);
        Label getNameLabel1 = new Label();
        methodVisitor.visitLabel(getNameLabel1);
        methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, getNameLabel0, getNameLabel1, 0);
        methodVisitor.visitMaxs(1, 1);
        methodVisitor.visitEnd();

        classWriter.visitEnd();
        byte[] bytes = classWriter.toByteArray();

             将字节流数组生成class文件

    try{
       FileOutputStream   fos = new FileOutputStream("F:/profile/asm/Student.class");
       fos.write(bytes);
       fos.close();
     }catch (Exception e){
       e.fillInStackTrace();
     }

          用idea或其他反编译工具打开,可以看到,生成源码和预想要求完全吻合

 2、分析字节码

            编写适配器逻辑

    public class TargetClassAdapter   extends ClassAdapter {

        private final  static Logger LOGGER =         LoggerFactory.getLogger(TargetClassAdapter.class);

        public TargetClassAdapter(ClassVisitor cv) {
            super(cv);
        }

         /**访问类属性*/
        @Override
        public FieldVisitor visitField(int access, String name, String desc, String signature, Object exceptions) {
            FieldVisitor fieldVisitor = cv.visitField(access, name, desc, signature, exceptions);
            LOGGER.info("visitField <--------> {} {} {}  {} {} ",access,name,desc,signature,exceptions);
            return  fieldVisitor;
    }
    
            /**访问类方法*/
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            LOGGER.info("visitMethod <--------> {} {} {}  {} {} ",access,name,desc,signature,exceptions);
            MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
            return methodVisitor;
        }
    }

             访问类的属性、方法

        ClassReader classReader =   new ClassReader(bytes);
        ClassWriter classWriter  =  new ClassWriter(ClassWriter.COMPUTE_MAXS);
        setMethod(classWriter);
        ClassVisitor classVisitor = new TargetClassAdapter(classWriter);
        classReader.accept(classVisitor,ClassReader.SKIP_DEBUG);

           这可以将类的属性和方法全部获取

三、ASM应用

       在java的许多框架里面,都能找到ASM的身影,比如AOP编程就可以利visitMethod对指定方法就行拦截,做前置后置增强,还有比如常用的插件Lombok就是利用ASM添加的setter getter方法。MyBatis的Mapper接口实现是通过动态代理实现,现在可以使用ASM动态创建实现了字节码来实现。

  1、定义接口

         定义StudentMapper接口

package org.example.cn.mapper;
import java.util.List;

public interface StudentMapper {
    List<String>  findStudentList();
}

2、生成实现类

        使用ASM生成实现类StudentMapperImpl

        ClassWriter classWriter = new ClassWriter(0);
        /// 参数列表第3个参数表示继承的父类,第4个参数表示实现的接口
        classWriter.visit(61, ACC_PUBLIC | ACC_SUPER, "org/example/cn/mapper/StudentMapperImpl", null, "java/lang/Object", new String[]{"org/example/cn/mapper/StudentMapper"});
        classWriter.visitSource("StudentMapperImpl.java", null);

        /// 定义构造器
        MethodVisitor  methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        methodVisitor.visitCode();
        Label initLabel0 = new Label();
        methodVisitor.visitLabel(initLabel0);
        methodVisitor.visitVarInsn(ALOAD, 0);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        methodVisitor.visitInsn(RETURN);
        Label initLabel1 = new Label();
        methodVisitor.visitLabel(initLabel1);
        /// 局部变量表,槽位的0处放的是this变量
        methodVisitor.visitLocalVariable("this", "Lorg/example/cn/mapper/StudentMapperImpl;", null, initLabel0, initLabel1, 0);
        methodVisitor.visitMaxs(1, 1);
        methodVisitor.visitEnd();

        /// 重写findStudentList方法
        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "findStudentList", "()Ljava/util/List;", "()Ljava/util/List<Ljava/lang/String;>;", null);
        methodVisitor.visitCode();
        Label label0 = new Label();
        methodVisitor.visitLabel(label0);
        ///  new ArrayList()
        methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");
        methodVisitor.visitVarInsn(ASTORE, 1);
        Label label1 = new Label();
        methodVisitor.visitLabel(label1);
        /// 压栈
        methodVisitor.visitVarInsn(ALOAD, 1);
        /// 赋值
        methodVisitor.visitLdcInsn("\u9648\u7476");
        ///调用add方法
        methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
        /// 出栈
        methodVisitor.visitInsn(POP);
        Label label2 = new Label();
        methodVisitor.visitLabel(label2);
        methodVisitor.visitVarInsn(ALOAD, 1);
        methodVisitor.visitLdcInsn("\u674e\u73b0");
        methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
        methodVisitor.visitInsn(POP);
        Label label3 = new Label();
        methodVisitor.visitLabel(label3);
        methodVisitor.visitLineNumber(11, label3);
        methodVisitor.visitVarInsn(ALOAD, 1);
        methodVisitor.visitLdcInsn("\u91d1\u6668");
        methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");
        methodVisitor.visitInsn(POP);
        Label label4 = new Label();
        methodVisitor.visitLabel(label4);
        methodVisitor.visitLineNumber(12, label4);
        methodVisitor.visitVarInsn(ALOAD, 1);
        methodVisitor.visitInsn(ARETURN);
        Label label5 = new Label();
        methodVisitor.visitLabel(label5);
        methodVisitor.visitLocalVariable("this", "Lorg/example/cn/mapper/StudentMapperImpl;", null, label0, label5, 0);
        methodVisitor.visitLocalVariable("list", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/String;>;", label1, label5, 1);
        methodVisitor.visitMaxs(2, 2);
        methodVisitor.visitEnd();

        classWriter.visitEnd();
        byte[] bytes = classWriter.toByteArray();

        字节码jjava源码

public class StudentMapperImpl implements StudentMapper {

    public List<String> findStudentList() {
        List<String> list = new ArrayList();
        list.add("陈瑶");
        list.add("李现");
        list.add("金晨");
        return list;
    }
}

3、加载实现类

          自定义类加载器加载StudentMapperImpl字节码

public class MyClassLoader  extends  ClassLoader{
 
    /// 类名全路径
    private final String   className;

    /// 字节码
    private final byte[]  bytes;

    public MyClassLoader( String  className,byte[]  bytes){
        this.className = className;
        this.bytes = bytes;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return  defineClass(className,bytes,0,bytes.length);
    }
}

         加载类

    String className = "org.example.cn.mapper.StudentMapperImpl";
    MyClassLoader myClassLoader = new MyClassLoader(className, bytes);
    Class<?>  clz =  myClassLoader.loadClass(className);

4、调用实现类

    ◆ 方法1

        利用构造器直接反射创建实例

   /// Class<?> aClass = Class.forName("org.example.cn.mapper.StudentMapperImpl");
   StudentMapper studentMapper = (StudentMapper) clz.getConstructor().newInstance();
   List<String> studentList = studentMapper.findStudentList();
   System.out.println("studentList---"+studentList);
    ◆ 方法2

       动态代理实例化接口

    StudentMapper studentMapper = (StudentMapper) Proxy.newProxyInstance(myClassLoader, new Class[]{StudentMapper.class}, (proxy, method, args1) -> method.invoke(clz.getConstructor().newInstance(), args1));
    List<String> studentList = studentMapper.findStudentList();
    System.out.println("studentList---"+studentList);

        运行结果

   

      ASM 操作字节码功能强大,但是有一定难度,需要对jvm字节码比较熟悉,用起来才会游刃有余,这种黑科技更适合造轮子框架,平常业务开发基本上很难碰到合适的应用场景。 

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

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

相关文章

仿真模拟--telnet服务两种认证模式(自作)

自己做的笔记,有问题或看不懂请见解一下~ 目录 两个路由器间实现telnet服务(password认证模式) server client 两个路由器间实现telnet服务(aaa认证模式) server client 改名 tab键补齐 不会就扣问号 ? save 两个路由器间实现telnet服务…

【车载开发系列】CAN通信总线再理解(中篇)

【车载开发系列】CAN通信总线再理解&#xff08;中篇&#xff09; 九. CAN总线标准十. CAN物理层十一. CAN数据链路层1&#xff09;CAN的通信帧类型2&#xff09;CAN的标准帧格式1. CAN ID2. 数据场 3&#xff09;CAN总线仲裁 十二. CAN应用层1&#xff09;CANopen2&#xff09…

蚓链数字化营销系统,开启企业无限可能!

蚓链数字化营销系统带给企业的不仅仅是卖货方案&#xff0c;其重要的是让企业具备拥有融合资源的能力、实现在更多业态跨界赚钱的能力、及打造品牌价值的能力。 在当今数字化时代&#xff0c;蚓链数字化营销系统正为企业带来新的变革与机遇。它所赋予企业的&#xff0c;绝非仅仅…

[Vulnhub] Sleepy JDWP+Tomcat+Reverse+Reverse-enginnering

信息收集 Server IP AddressPorts Opening192.168.8.100TCP:21,8009,9001 $ nmap -sV -sC 192.168.8.100 -p- --min-rate 1000 -Pn Starting Nmap 7.92 ( https://nmap.org ) at 2024-06-20 05:06 EDT Nmap scan report for 192.168.8.100 (192.168.8.100) Host is up (0.00…

Python学习打卡:day11

day11 笔记来源于&#xff1a;黑马程序员python教程&#xff0c;8天python从入门到精通&#xff0c;学python看这套就够了 目录 day1183、自定义 Python 包创建包导入包方式1方式2方式3方式4 84、安装第三方包安装第三方包——pippip的网络优化 安装第三方包——PyCharm 85、…

【DKN: Deep Knowledge-Aware Network for News Recommendation】

DKN: Deep Knowledge-Aware Network for News Recommendation 摘要 在线新闻推荐系统旨在解决新闻信息爆炸的问题&#xff0c;为用户进行个性化推荐。 总体而言&#xff0c;新闻语言高度凝练&#xff0c;充满知识实体和常识。 然而&#xff0c;现有的方法并没有意识到这些外部…

Cesium源码解析六(3dtiles属性获取、建筑物距离计算、建筑物着色及其原理分析)

快速导航 Cesium源码解析一&#xff08;搭建开发环境&#xff09; Cesium源码解析二&#xff08;terrain文件的加载、解析与渲染全过程梳理&#xff09; Cesium源码解析三&#xff08;metadataAvailability的含义&#xff09; Cesium源码解析四&#xff08;metadata元数据拓展…

【因果推断python】46_估计量2

目录 连续型干预变量案例 非线性处理效果 关键思想 连续型干预变量案例 目标转换方法的另一个明显缺点是它仅适用于离散或二元处理。这是你在因果推理文献中经常看到的东西。大多数研究都是针对二元干预案例进行的&#xff0c;但您找不到很多关于连续干预的研究。这让我很困…

Javase.String 类

String 类 【本节目标】1. String类的重要性2. 常用方法2.1 字符串构造2.2 String对象的比较2.3 字符串查找2.4 转化2.5 字符串替换2.7 字符串截取2.8 其他操作方法2.9 字符串的不可变性2.10 字符串修改 3. StringBuilder和StringBuffer3.2 面试题&#xff1a; 4. String类oj4.…

使用虚拟滚动条优化通过el-collapse展示多条数据的性能问题

我们将一个10000条的数据通过el-collapse展示出来&#xff0c;同时在点开每一个item时&#xff0c;要内置一个编辑器&#xff0c;对文本内容进行编辑。其实&#xff0c;如果仅10000条数据的文本的单独展示&#xff0c;可能性能不会太差&#xff0c;但由于每一条都需要带有一个文…

web中间件漏洞-jboss部署war包

web中间件漏洞-jboss部署war包 攻击机服务器准备好的ma.war

FreeBSD在zfs挂接第二块ssd 硬盘

为FreeBSD机器新增加了一块ssd硬盘&#xff1a;骑尘 256G 先格式化分区硬盘 进入bsdconfig 选Disk Management 选择ada1 &#xff0c;也就是新增加的硬盘 选择auto 然后选择Entire Disk 提示信息 The existing partition scheme on this disk (MBR) │ …

Python数据科学 | 是时候跟Conda说再见了

本文来源公众号“Python数据科学”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;是时候跟Conda说再见了 1 简介 conda作为Python数据科学领域的常用软件&#xff0c;是对Python环境及相关依赖进行管理的经典工具&#xff0c;通…

容声神助攻!欧洲杯最刺激一战诞生,神来之笔背后有高人

2-2&#xff0c;当比分最终被定格在这个数字时&#xff0c;克罗地亚的老将们即使职业生涯已经经历了太多太多惨烈的比赛&#xff0c;此刻依然难掩心中的复杂情绪。 欧洲杯开赛至今最刺激的一战&#xff0c;从0-1落后的长时间焦虑&#xff0c;到下半场3分钟扳平反超的狂喜&…

【嵌入式Linux】<总览> 文件IO(更新中)

文章目录 前言 一、常用函数 1. open函数 2. close函数 3. write函数 4. read函数 5. dup函数 6. dup2函数 二、文件读写细节 1. 换行符 2. 文件描述符 3. errno和perror 前言 在Linux系统中&#xff0c;一切皆文件。因此&#xff0c;掌握Linux下文件IO常用的函数…

为什么美业门店要用专业的美业系统?博弈美业SaaS管理系统Java源码分享

美容、医美等美业门店需要使用专业的美业系统&#xff0c;而不是普通的管理系统&#xff0c;美业专用系统的优势在哪&#xff1f; 专业的美业系统与普通系统相比&#xff0c;更加贴合美业门店的经营需求&#xff0c;提供了更全面、便捷、高效的管理功能&#xff0c;有助于提升…

端到端自动驾驶的基础概念

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 目录 1.端到端自动驾驶的定义 1.1特斯拉FSD 1.2端到端架构演进 1.3大模型 1.4世界模型 1.5纯视觉传感器 2.落地的挑战 1.端到端自动驾驶的定…

RSA学习

[MRCTF2020]Easy_RSA 先来分析一下这个RSA代码的特殊性&#xff0c;这个不是传统的RSA,随机生成N&#xff0c;并保证为N%8的余数是5 zlib 用于数据压缩&#xff0c;但是并似乎没有用到 gen_p(): 生成随机的1024位质数p。计算np*q&#xff0c;并没有直接用于加密。计算F_n…

返回给前端数据的封装

返回格式如下&#xff1a; { "code": 200/400, "msg": "成功"/"失败", "total": n, "data": [ {}&#xff0c;{}]} 1.在common中新增Result 类&#xff0c;代码如下 package com.xxx0523.common; import lombo…

【OpenVINO™】使用 OpenVINO™ C# 异步推理接口部署YOLOv8 ——在Intel IGPU 上速度依旧飞起!!

OpenVINO Runtime支持同步或异步模式下的推理。Async API的主要优点是&#xff0c;当设备忙于推理时&#xff0c;应用程序可以并行执行其他任务&#xff08;例如&#xff0c;填充输入或调度其他请求&#xff09;&#xff0c;而不是等待当前推理首先完成。 当我们使用异步API时&…