字节码编程ASM之插桩调用其他类的静态方法

news2024/12/27 0:32:25

写在前面

源码 。
本文看下通过ASM如何实现插桩调用其他类的静态方法。

1:编码

假定有如下的类:

public class PayController {
    public void pay(int userId, int payAmount) {
        System.out.println("用户:" + userId + ", 调用支付系统完成支付" + payAmount + ",准备发货!");
        return;
    }
}

现在呢,假定有如下的日志审计类,用来记录日志信息:

/**
 * 日志审计工具类
 */
public class AuditLogUtil {
    public static void infoLog(String funcName, int... params) {
        System.out.println("方法:" + funcName + ", 参数:" + "[" + params[0] + "," + params[1] + "]");
    }
}

现在有一个需求,需要在调用PayController#pay方法时,增加审计日志的记录,也就是像下面这样的代码:

public class PayController {
    public void pay(int userId, int payAmount) {
        AuditLogUtil.infoLog("pay", new int[] {userId, payAmount});
        System.out.println("用户:" + userId + ", 调用支付系统完成支付" + payAmount + ",准备发货!");
        return;
    }
}

但是,该需求并不是一直有的,也就最近半年需要,如果硬编码来做,显然不是一个很好的方案,所以啊,使用插桩再结合javaagent来实现,就是很好的方案了!本部分就来看下如何进行插桩,直接来看代码吧:

package com.dahuyou.asm.callOuterCls;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;

import static org.objectweb.asm.Opcodes.ASM5;

public class CallOuterMethodEnhancer extends ClassLoader {

    public static void main(String[] args) throws Exception {
        // 读取要插桩加强的类
        ClassReader cr = new ClassReader(PayController.class.getName());
        // 准备往要插桩加强的类中写内容
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        // 准备插桩
        ClassVisitor cv = new ProfilingClassAdapter(cw, PayController.class.getSimpleName());
        // 正式插桩
        cr.accept(cv, ClassReader.EXPAND_FRAMES);
        // 获取插桩后的代码
        byte[] bytes = cw.toByteArray();

        // 反射执行插桩后的字节码
        Class<?> clazz = new CallOuterMethodEnhancer().defineClass("com.dahuyou.asm.callOuterCls.PayController", bytes, 0, bytes.length);
        // 反射获取 main 方法
        Method method = clazz.getMethod("pay", int.class, int.class);
        Object obj = method.invoke(clazz.newInstance(), 69089, 285);
        System.out.println("结果:" + obj);

        outputClazz(bytes);
    }

    static class ProfilingClassAdapter extends ClassVisitor {

        public ProfilingClassAdapter(final ClassVisitor cv, String innerClassName) {
            super(ASM5, cv);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if (!"pay".equals(name)) return super.visitMethod(access, name, descriptor, signature, exceptions);
            MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
            return new ProfilingMethodVisitor(mv, access, name, descriptor);
        }
    }

    static class ProfilingMethodVisitor extends AdviceAdapter {

        private String name;

        protected ProfilingMethodVisitor(MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(ASM5, methodVisitor, access, name, descriptor);
            this.name = name;
        }

        @Override
        public void visitVarInsn(int opcode, int var) {
            super.visitVarInsn(opcode, var);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
            super.visitFieldInsn(opcode, owner, name, descriptor);
        }

        /**
         * 实现效果:
         *     public void pay(int userId, int payAmount) {
         *         AuditLogUtil.infoLog("pay", new int[] {userId, payAmount});
         *         System.out.println("用户:" + userId + ", 调用支付系统完成支付" + payAmount + ",准备发货!");
         *         return;
         *     }
         * 其中AuditLogUtil.infoLog("pay", new int[] {userId, payAmount});就是要插桩的代码
         */
        @Override
        protected void onMethodEnter() {
            // ldc 加载方法名称常量
            mv.visitLdcInsn(name); // 方法名称压到栈顶 此时栈:pay
            mv.visitInsn(ICONST_2); // 将int型2推送至栈顶 此时栈:2, pay
            mv.visitIntInsn(NEWARRAY, T_INT); // 获取栈顶元素,并以其为长度创建一个数组,并将其引用压倒栈顶 此时栈:new int[]{}, pay
            mv.visitInsn(DUP); // 复制栈顶元素并压到栈顶 此时栈:new int[], new int[], pay
            mv.visitInsn(ICONST_0); // 将常量0压到栈顶 此时栈:0, new int[], new int[], pay
            mv.visitVarInsn(ILOAD, 1); // 将本地变量表1位置变量压倒栈顶  1位置变量, 此时栈:0, new int[], new int[], pay
            mv.visitInsn(IASTORE); // 将栈顶int型数值存入指定数组的指定索引位置 new int[0] = 1位置变量,此时栈new int[], pay
            mv.visitInsn(DUP); // 复制栈顶元素 此时栈:new int[], new int[], pay
            mv.visitInsn(ICONST_1); // 加载常量1 此时栈:1, new int[], new int[], pay
            mv.visitVarInsn(ILOAD, 2); // 加载本地变量表slot 2变量 此时栈:2位置变量, 1, new int[], new int[], pay
            mv.visitInsn(IASTORE); // 栈顶元素存储到数组 new int[1] = 2位置变量 此时栈:new int[], pay
            mv.visitMethodInsn(INVOKESTATIC, "com/dahuyou/asm/callOuterCls/AuditLogUtil", "infoLog", "(Ljava/lang/String;[I)V", false); // 调用静态方法infoLog,参数为当前栈的new int[], pay,完成打印
        }
    }

    private static void outputClazz(byte[] bytes) {
        // 输出类字节码
        FileOutputStream out = null;
        try {
            String pathName = CallOuterMethodEnhancer.class.getResource("/").getPath() + "AsmCallOuterMethodEnhancer.class";
            out = new FileOutputStream(new File(pathName));
            System.out.println("ASM类输出路径:" + pathName);
            out.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != out) try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

代码比较长,其中比较关键代码为:

ClassReader cr = new ClassReader(PayController.class.getName());
    读取要插装增强的类准备插装
ClassVisitor cv = new ProfilingClassAdapter(cw, PayController.class.getSimpleName());
    进行插装,具体是在com.dahuyou.asm.methodWasteTime.TestMonitor.ProfilingClassAdapter#visitMethod中返回自定义的methodvisitor实现插装
static class ProfilingMethodVisitor extends AdviceAdapter
    methovisitor插装切面类,onMethodEnter方法插装方法执行前的逻辑,onMethodExit插装方法执行后的逻辑
byte[] bytes = cw.toByteArray();
    这就拿到插装后的字节码了

主要看法方法onMethodEnter,完成了插桩代码AuditLogUtil.infoLog("pay", new int[] {userId, payAmount});,已经写了比较详细的注释,还有哪里看不懂的话,就留言告诉我。

接着运行测试:
在这里插入图片描述
为了更加清晰,看下生成的插桩后的字节码:
在这里插入图片描述

写在后面

参考文章列表

JVM 虚拟机字节码指令表 。

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

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

相关文章

GPIO和PIN

文章目录 1 GPIO和Pin1.1 GPIO和Pin基础概念1.2 GPIO输入模式1.3 GPIO输出模式1.4 GPIO的HAL库1.4.1 一些HAL库表示1.4.2 HAL库常用GPIO函数1.4.3 GPIO点亮led灯程序例子 1 GPIO和Pin 1.1 GPIO和Pin基础概念 ​ 单片机有很多的引脚&#xff0c;为了操控每一个引脚&#xff0c…

ChatGPT之母:AI自动化将取代人类,创意性工作或将消失

目录 01 AI取代创意性工作的担忧 1.1 CTO说了啥 02 AI已开始大范围取代人类 01 AI取代创意性工作的担忧 几天前的采访中&#xff0c;OpenAI的CTO直言&#xff0c;AI可能会扼杀一些本来不应该存在的创意性工作。 近来一篇报道更是印证了这一观点。国外科技媒体的老板Miller用…

EC-R3588SPC 资料介绍

EC-R3588SPC 资料介绍 1 介绍1.1 简介1.2 资料链接1.3 硬件资源1.4 资源下载 2 升级固件2.1 启动模式说明2.1.1 前言2.1.2 如何获取固件2.1.3 升级方法2.1.4 启动媒体2.1.5 引导模式2.1.5.1 正常模式2.1.5.2 装载机模式2.1.5.3 MaskRom模式 2.2 通过USB线升级固件2.2.1 介绍2.2…

树 | 第6章 | Java版大话数据结构 | 1.7w字长文 | 二叉树 | 哈夫曼树 | 二叉树遍历 | 构造二叉树 | LeetCode练习

&#x1f4cc;本篇分享的大话数据结构中&#x1f384;树&#x1f384;这一章的知识点&#xff0c;在此基础上&#xff0c;增加了练习题帮助大家理解一些重要的概念✅&#xff1b;同时&#xff0c;由于原文使用的C语言代码&#xff0c;不利于学习Java语言的同学实践&#xff0c;…

endswith()方法——是否以指定子字符串结尾

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 endswith()方法用于检索字符串是否以指定子字符串结尾。如果是则返回True&#xff0c;否则返回False。endswith()方法的语法格式如下&…

GenAI 用于客户支持 — 第 1 部分:构建我们的概念验证

作者&#xff1a;来自 Elastic Chris Blaisure 欢迎来到 Inside Elastic 博客系列&#xff0c;我们将展示 Elastic 的内部运营如何解决实际业务挑战。本系列将揭示我们将生成式 AI&#xff08;gererative AI - GenAI&#xff09;集成到客户成功和支持运营中的历程&#xff0c;让…

【C++】类、静态、枚举、重载、多态、继承、重写、虚函数

五、类 面向对象编程是一个巨大的编程范式。C中的类class就是基于对象的程序设计。 我们可以用类来定义一个新的类型&#xff0c;这些新类型就可以像内置类型一样使用。 内置类型颗粒度太太小&#xff0c;现实需求又非常复杂&#xff0c;这就需要我们把内置类型适度的进行拼搭…

在Redis中使用Lua脚本实现多条命令的原子性操作

Redis作为一个高性能的键值对数据库&#xff0c;被广泛应用于各种场景。然而&#xff0c;在某些情况下&#xff0c;我们需要执行一系列Redis命令&#xff0c;并确保这些命令的原子性。这时&#xff0c;Lua脚本就成为了一个非常实用的解决方案。 问题的提出 假设我们有一个计数…

Redis为什么设计多个数据库

​关于Redis的知识前面已经介绍过很多了,但有个点没有讲,那就是一个Redis的实例并不是只有一个数据库,一般情况下,默认是Databases 0。 一 内部结构 设计如下: Redis 的源码中定义了 redisDb 结构体来表示单个数据库。这个结构有若干重要字段,比如: dict:该字段存储了…

Redis-实战篇-缓存更新策略(内存淘汰、超时剔除、主动更新)

文章目录 1、缓存更新策略1.1、内存淘汰1.2、超时剔除1.3、主动更新 2、业务场景&#xff1a;3、主动更新在企业中业务实现有三种方式3.1、Cache Aside Pattern3.1.1、操作缓存和数据库时有三个问题需要考虑&#xff1a;3.1.1.1、删除缓存还是更新缓存&#xff1f;3.1.1.2、如何…

信息学奥赛初赛天天练-39-CSP-J2021基础题-哈夫曼树、哈夫曼编码、贪心算法、满二叉树、完全二叉树、前中后缀表达式转换

PDF文档公众号回复关键字:20240629 2022 CSP-J 选择题 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 5.对于入栈顺序为a,b,c,d,e的序列&#xff0c;下列( )不合法的出栈序列 A. a&#xff0c;b&a…

架构师必知的绝活-JVM调优

前言 为什么要学JVM&#xff1f; 首先&#xff1a;面试需要 了解JVM能帮助回答面试中的复杂问题。面试中涉及到的JVM相关问题层出不穷&#xff0c;难道每次面试都靠背几百上千条面试八股&#xff1f; 其次&#xff1a;基础知识决定上层建筑 自己写的代码都不知道是怎么回事&a…

show-overflow-tooltip 解决elementui el-table标签自动换行的问题

elementui中 el-table中某一行的高度不想因为宽度不够而撑开换行展示的解决方法。可通过show-overflow-tooltip属性解决&#xff0c;如下 代码是这样的 <el-table-column width"80" prop"id" label"ID"></el-table-column> <el…

java基于ssm+jsp 二手车交易网站

1用户功能模块 定金支付管理&#xff0c;在定金支付管理页面可以填写订单编号、车型、品牌、分类、车身颜色、售价、订金金额、付款日期、备注、用户名、姓名、联系方式、是否支付等信息&#xff0c;进行详情、修改&#xff0c;如图1所示。 图1定金支付管理界面图 预约到店管…

亨廷顿(Huntington)方法-名额分配

前言 20世纪初&#xff0c;美国人口普查局局长约瑟夫A亨廷顿&#xff08;Joseph A. Hill&#xff09;和数学家爱德华V亨廷顿&#xff08;Edward V. Huntington&#xff09;在研究议会议席分配问题时&#xff0c;提出了一种基于数学原理的算法。该算法通过计算每个州的人口比例&…

有趣的仿神经猫html5圈小猫游戏源码

有趣的仿神经猫html5圈小猫游戏源码,点击小圆点&#xff0c;围住小猫游戏。猫已经跑到地图边缘&#xff0c;你输了。内含json数据&#xff0c;部署到服务器方可运行 微信扫码免费获取源码

【自然语言处理系列】掌握jieba分词器:从基础到实战,深入文本分析与词云图展示

本文旨在全面介绍jieba分词器的功能与应用&#xff0c;从分词器的基本情况入手&#xff0c;逐步解析全模式与精确模式的不同应用场景。文章进一步指导读者如何通过添加自定义词典优化分词效果&#xff0c;以及如何利用jieba分词器进行关键词抽取和词性标注&#xff0c;为后续的…

基于docker安装redis服务

Redis是我们在项目中经常需要使用的缓存数据库&#xff0c;安装redis的方式也有很多&#xff0c;本文主要是给大家讲解如何基于docker进行redis服务的安装&#xff0c;主要介绍&#xff0c;如何拉取redis镜像、如何挂载redis的数据以及使用redis的配置文件和开启认证等功能&…

【MySQL】InnoDB架构

本文MySQL版本是8.X版本 这是官方文档给出来的架构图&#xff1a;MySQL :: MySQL 8.0 Reference Manual :: 17.4 InnoDB Architecture 可以看出&#xff0c;整体上是分成两部分的&#xff1a;内存结构(提高效率)和磁盘结构(数据持久化)&#xff0c;下面将把每个区域都大致做一个…

RFID固定资产管理系统在企业中的应用与优势

随着企业资产规模的不断扩大和管理复杂性的增加&#xff0c;传统的资产管理方式已无法满足企业高效管理的需求。RFID固定资产管理系统凭借其高效、准确、实时的特点&#xff0c;成为企业固定资产管理的新宠。 一、什么是RFID固定资产管理系统 RFID&#xff08;无线射频识别&…