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

写在前面

源码 。
本文看下通过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 虚拟机字节码指令表 。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mybatis框架介绍 , 环境的搭建和代码实现

1.mybatis框架介绍 mybatis框架介绍 mybatis是Apache软件基金会下的一个开源项目&#xff0c;前身是iBatis框架。2010年这个项目由apache 软件基金会迁移到google code下&#xff0c;改名为mybatis。2013年11月又迁移到了github(GitHub 是一个面向开源及私有 软件项目的托管平…

kafka--发布-订阅消息系统

1. Kafka概述 1. kafka是什么 kafka是分布式的、高并发的、基于发布/订阅模式的消息队列软件系统。 kafka中的重要组件 Producer&#xff1a;消息生产者&#xff0c;发布消息到Kafka集群的终端或服务Consume&#xff1a;消费者&#xff0c;从Kafka集群中消费消息的终端或服…

GPT-5

欢迎来到 Papicatch的博客 文章目录 &#x1f349;技术突破预测 &#x1f348;算法进步 &#x1f348;理解力提升 &#x1f348;行业推动力 &#x1f349;人机协作的未来 &#x1f348;辅助决策 &#x1f348;增强创造力 &#x1f348;复杂任务中的角色 &#x1f348;人…

构建以caffeine为L1,Redis为L2的多级缓存

&#x1f3c3;‍♂️ 微信公众号: 朕在debugger© 版权: 本文由【朕在debugger】原创、需要转载请联系博主&#x1f4d5; 如果文章对您有所帮助&#xff0c;欢迎关注、点赞、转发和订阅专栏&#xff01; 前言 S&#xff08;Situation&#xff09;&#xff1a;业务代码与缓存…

RK3588 Android13 TvSetting 中增加 WebView 切换菜单

前言 电视产品,客户要求在设置中设备偏好设置子菜单下增加一个 WebView切换菜单,一开始不知道怎么下手,后来想起来在设置开发者选项里有一个类似的菜单, 去把实现逻辑搞出来应该就ok。 效果图 TvSetting 部分修改文件清单 packages/apps/TvSettings/Settings/res/values…

改机软件有哪些?实现一键新机、改串号、改IMEI和手机参数的需求 硬改手机软件,新机环境模拟 设备伪装,一键改机,一键复原

这次针对可以直接开端口修改参数的机型做一些工具解析 前面接触合作过很多工作室。其中很多工作室对于各自软件的跳验证有各自的需求。 一个机型各项参数一般有IMEI WiFi 蓝牙 sn psb ESN等等。 针对这些参数的修改首先要明白各自软件检测的具体是哪些参数来验证。 对于常用…

在Ubuntu上配置PPPoE服务:从安装到自动化启动的全指南

在Ubuntu上配置PPPoE服务&#xff1a;从安装到自动化启动的全指南 PPPoE&#xff08;点对点协议以太网&#xff09;是一种广泛用于DSL和光纤宽带连接的协议。在本篇技术博客中&#xff0c;我们将详细介绍如何在Ubuntu系统上配置PPPoE服务&#xff0c;包括安装、配置、启动以及…

STM32——使用TIM输出比较产生PWM波形控制舵机转角

一、输出比较简介&#xff1a; 只有高级定时器和通用寄存器才有输入捕获/输出比较电路&#xff0c;他们有四个CCR&#xff08;捕获/比较寄存器&#xff09;&#xff0c;共用一个CNT&#xff08;计数器&#xff09;&#xff0c;而输出比较功能是用来输出PWM波形的。 红圈部分…

深入探索大模型的魅力:前沿技术、挑战与未来展望

目录 一、大模型的前沿技术 二、大模型面临的挑战 三、大模型的未来展望 四、总结 在当今人工智能领域&#xff0c;大模型不仅是一个热门话题&#xff0c;更是推动技术进步的重要引擎。从深度学习的浪潮中崛起&#xff0c;大模型以其卓越的性能和广泛的应用前景&#xff0c…

中医对于帕金森病的病因和症状有何解释?

中医对帕金森病的病因解释 中医认为帕金森病的病因复杂多样&#xff0c;涉及多个方面。首先&#xff0c;精神因素如长期的情绪抑郁、悲伤、忧虑等精神不畅可能导致气机郁结&#xff0c;气血运行障碍&#xff0c;进而影响脑部神经系统的功能。其次&#xff0c;肝郁气滞也被认为…

2025艺考时间线来啦!所有艺考生码住!

2025届艺考生们的征途即将启程。对于每一个即将参加艺考的考生和家长来说&#xff0c;梳理艺考时间节点是尤为重要的。 对于艺考生而言&#xff0c;更早的规划意味着更充分的准备时间&#xff0c;更扎实的专业能力。补齐艺考信息差&#xff0c;以下2025艺考时间线一定要看明白…

CC7关于ConstantTransformer返回值不能和put一样的分析

CC7关于ConstantTransformer返回值不能和put一样的分析 前言 实验室的gaorenyusi也是学到cc7的时候问了我一个很好的问题&#xff0c;我当时学的时候没有在意&#xff0c;然后就去调试分析解决了一下 分析 首先是paylaod package CC7;import org.apache.commons.collectio…

Mysql基本知识点

1.数据库的基本操作 显示当前的数据库 show databases;创建一个数据库 直接创建数据库 create database 数据库名字;如果系统没有 test2 的数据库&#xff0c;则创建一个名叫 test2 的数据库&#xff0c;如果有则不创建 create database if not exists test2;如果系统没有 db…

Mathematica训练课(44)-- 一些符号#,,//, /. 的整理

①“//”在后面写成你要执行的操作,即可执行。 注意:这一函数作用域标志的优先级是很靠后的,也就是说它会对一整行式子作用。 ②@的作用是在@后面的第一个元素进行操作 Sqrt @ a(*@作用在@后面、对离@最近的仅仅一个元素作用*) 例如,下面 若作用对象外面套着{},那么就要…

学校消防设施设备管理系统

建立和落实校园消防安全管理责任制,做到消防安全工作有人专管,部门和岗位有人落实的日常管理&#xff0c;及时发现消防安全隐患,及时反映,及时处理,杜绝校园内消防安全隐患。 凡尔码平台搭建学校消防设施设备管理系统可以通过设备管理系统对消防器材设施基本信息、设施有效期、…

Unity2D - 状态机(State Machine)详解

1. 状态机概述 在角色的生成中&#xff0c;由于事件的不同&#xff0c;动作的不同&#xff0c;角色会处于不同的状态中。例如对战冒险游戏&#xff0c;面临Boss的攻击&#xff0c;角色会受到例如中毒&#xff0c;恐惧等Debuff效果&#xff0c;若单纯的在一个脚本中使用if等语句…

中霖教育靠谱吗?在职备考一建好通过吗?

中霖教育靠谱吗?在职备考一建好通过吗? 课程设置&#xff1a;报名后会进行测评&#xff0c;了解学员的知识掌握情况、时间安排和记忆思维特点等&#xff0c;制定更适合的学习计划。 课程以考试通过为目标&#xff0c;去繁化简&#xff0c;只讲有用的干货&#xff0c;帮助快…

ASUS/华硕幻14 2023 GA402X系列 原厂Windows11-22H2系统

安装后恢复到您开箱的体验界面&#xff0c;带原机所有驱动和软件&#xff0c;包括myasus mcafee office 奥创等。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http:…

python基础语法 004-1流程控制- 条件控制

1 条件控制 1.1 表达 条件表达式冒号缩进 1.1.1 单个条件&#xff1a;满足表达式 """ ############if的表示 if 条件表达式:(缩进)条件满足以后要运行的代码例子: #遇到冒号要缩进 #缩进&#xff1a;1个缩进用4个空格&#xff0c;整个篇幅缩进需要统一 #4个…

如何解决三菱软件提示 起动MELSOFT Mediative Server失败

前言&#xff1a; 注意&#xff0c;这篇文章仅针对如何解决 起动MELSOFT Mediative Server失败 的问题。对于其他相关的问题&#xff0c;请搜索其他相应的解决办法。 本人是在重装三菱GX Works软件时遇到此问题的。后来搜索发现无人能妥善的关闭这个提示。因此本文介绍如何关…