字节码编程ASM之插桩方法执行耗时

写在前面

本文看下如何对已有类进行插装。以最经典的方法执行耗时作为例子。

1:编码

假定有如下的代码:

public class MyMethod {

    public String queryUserInfo(String uid) {
        System.out.println("xxxx");
        System.out.println("xxxx1");
        System.out.println("xxxx2");
        System.out.println("xxxx3");
        return uid;
    }

}

动态插装生成如下的代码:

public String queryUserInfo(String uid) {
    long var2 = System.nanoTime();
    System.out.println("xxxx");
    System.out.println("xxxx1");
    System.out.println("xxxx2");
    System.out.println("xxxx3");
    System.out.println("方法执行耗时(纳秒)->queryUserInfo:" + (System.nanoTime() - var2));
    return uid;
}

插装代码如下:

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

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

public class TestMonitor extends ClassLoader {

    public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

        // 读取需要插装的类
        ClassReader cr = new ClassReader(MyMethod.class.getName());
//        ClassReader cr = new ClassReader("com.dahuyou.asm.methodWasteTime.MyMethod11");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);

        // 为什么要重新生成构造函数???
        {
            MethodVisitor methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(Opcodes.RETURN);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }
        ClassVisitor cv = new ProfilingClassAdapter(cw, MyMethod.class.getSimpleName());
        cr.accept(cv, ClassReader.EXPAND_FRAMES);

        byte[] bytes = cw.toByteArray();
        Class<?> clazz = new TestMonitor().defineClass("com.dahuyou.asm.methodWasteTime.MyMethod", bytes, 0, bytes.length);
        Method queryUserInfo = clazz.getMethod("queryUserInfo", String.class);
        Object obj = queryUserInfo.invoke(clazz.newInstance(), "10001");
        System.out.println("测试结果:" + obj);

        // 写到class文件看以下,对于完成插装没有实际作用
        outputClazz(bytes);

    }

    static class ProfilingClassAdapter extends ClassVisitor {

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

        // asm在生成字节码的时候每个方法都会调用一次这个方法,让我们插装
        public MethodVisitor visitMethod(int access,
                                         String name,
                                         String desc,
                                         String signature,
                                         String[] exceptions) {
            System.out.println("access:" + access);
            System.out.println("name:" + name);
            System.out.println("desc:" + desc);
            // 因为只插装queryUserInfo,方法所以其他方法直接返回null,保持不动了
            if (!"queryUserInfo".equals(name)) return null;

            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            // org.objectweb.asm.commons.AdviceAdapter子类,onMethodEnter方法开始前调用,onMethodExit方法执行结束时调用
            return new ProfilingMethodVisitor(mv, access, name, desc);
        }

    }

    static class ProfilingMethodVisitor extends AdviceAdapter {

        private String methodName = "";

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

        /*
        方法前生成代码 long var2 = System.nanoTime();
         */
        @Override
        protected void onMethodEnter() {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
            mv.visitVarInsn(LSTORE, 2);
            mv.visitVarInsn(ALOAD, 1);
        }

        /*
        方法后生成代码System.out.println("方法执行耗时(纳秒)->queryUserInfo:" + (System.nanoTime() - var2));
         */
        @Override
        protected void onMethodExit(int opcode) {
            if ((IRETURN <= opcode && opcode <= RETURN) || opcode == ATHROW) {
                mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

                mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
                mv.visitLdcInsn("方法执行耗时(纳秒)->" + methodName+":");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

                mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
                mv.visitVarInsn(LLOAD, 2);
                mv.visitInsn(LSUB);

                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

            }
        }
    }

    private static void outputClazz(byte[] bytes) {
        // 输出类字节码
        FileOutputStream out = null;
        try {
            String pathName = TestMonitor.class.getResource("/").getPath() + "AsmTestMonitor.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(MyMethod.class.getName());
    读取要插装增强的类准备插装
ClassVisitor cv = new ProfilingClassAdapter(cw, MyMethod.class.getSimpleName());
    进行插装,具体是在com.dahuyou.asm.methodWasteTime.TestMonitor.ProfilingClassAdapter#visitMethod中返回自定义的methodvisitor实现插装
static class ProfilingMethodVisitor extends AdviceAdapter
    methovisitor插装切面类,onMethodEnter方法插装方法执行前的逻辑,onMethodExit插装方法执行后的逻辑
byte[] bytes = cw.toByteArray();
    这就拿到插装后的字节码了

运行测试:
在这里插入图片描述
生成的字节码如下:
在这里插入图片描述

写在后面

参考文章列表

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

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

相关文章

可的哥Codigger项目体检是衡量代码质量标准

在飞速发展的现代商业世界中&#xff0c;项目能否成功的核心要素是项目质量&#xff0c;也就是其健康状态。为了确保项目顺利进行并达到预期目标&#xff0c;项目体检工具&#xff08;Health Check&#xff09;&#xff0c;简称“项目体检”&#xff0c;变得尤为重要。可的哥&a…

一分钟学习数据安全—自主管理身份SSI分布式标识DID介绍

SSI标准化的两大支柱&#xff0c;一个是VC&#xff0c;之前简单介绍过&#xff0c;另一个就是DID。基本层次上&#xff0c;DID就是一种新型的全局唯一标识符&#xff0c;跟浏览器的URL没有什么不同。深层次上&#xff0c;DID是互联网分布式数字身份和PKI新层级的原子构件。 一…

猫咪主食冻干哪个牌子好?希喂、SC、鲜朗人气养猫好物强烈推荐

目前主食冻干市场产品良莠不齐&#xff0c;一些主食冻干品牌一味追求堆砌营养值和利润&#xff0c;实际毫不关心猫咪食品健康&#xff0c;不仅存在肉粉冒充鲜肉、临期改日期卖等问题&#xff0c;甚至出现并为送检第三方、细菌超标等情况&#xff0c;严重的甚至危及猫咪生命&…

从单点到全景:视频汇聚/安防监控EasyCVR全景视频监控技术的演进之路

在当今日新月异的科技浪潮中&#xff0c;安防监控领域的技术发展日新月异&#xff0c;全景摄像机便是这一领域的杰出代表。它以其独特的360度无死角监控能力&#xff0c;为各行各业提供了前所未有的安全保障&#xff0c;成为现代安防体系中的重要组成部分。 一、全景摄像机的技…

ISO 50001能源管理体系:激活绿色动能和共塑可持续发展

在当今全球化加速和工业化水平不断提高的背景下&#xff0c;能源消费呈现出前所未有的增长趋势。然而&#xff0c;能源资源的有限性、能源价格的波动以及能源消费对环境造成的影响&#xff0c;尤其是温室气体排放导致的全球气候变化问题&#xff0c;已经成为全球关注的焦点。为…

2024 6.17~6.23 周报

一、上周工作 吴恩达的机器学习、实验-回顾之前密集连接部分 二、本周计划 继续机器学习&#xff0c;同时思考实验如何修改&#xff0c;开始整理代码 三、完成情况 3.1 多类特征、多元线性回归的梯度下降、特征缩放、逻辑回归 多类特征&#xff1a; 多元线性回归的梯度下…

远程工具的使用

远程连接工具的作用&#xff0c;通过远程连接到服务器上&#xff0c;方便操作&#xff01; 1.常见的远程连接工具 XShell&#xff1a;这是一款Windows平台下的SSH客户端软件&#xff0c;支持SSH1、SSH2、SFTP、TELNET、RLOGIN等多种协议&#xff0c;功能丰富&#xff0c;包…

frida的安装使用以及解决抓包app时遇到的证书校验

frida的安装和使用 这里使用夜神模拟器来演示frida的使用&#xff0c;因为真机开启frida-server服务时需要root权限,模拟器自带root 下载夜神模拟器并启动 夜神官网 打开power shell&#xff0c; adb连接模拟器&#xff0c;查看模拟器的系统型号 adb connect 127.0.0.1:6200…

解锁高效运维新纪元:网络基础设施数字孪生管理工具

随着信息技术的飞速发展&#xff0c;网络基础设施的运维管理变得日益复杂。北京耐威迪科技股份有限公司凭借其创新技术&#xff0c;推出了nVisual网络基础设施数字孪生管理工具&#xff0c;这一革命性的解决方案不仅提升了运维效率&#xff0c;更在成本节约和项目进度上实现了突…

【Redis】Set 集合常用命令以及使用场景

集合&#xff08;Set&#xff09;类型的值是字符串的无序集合&#xff0c;并且每个值都是唯一的。本文将介绍 Redis Set 的常用命令包含示例、Set的内部编码以及使用场景。 集合类型也是保存多个字符串类型的元素的&#xff0c;但和列表类型不同的是&#xff0c;集合中 1)元素…

2024最新总结:1500页金三银四面试宝典 记录35轮大厂面试(都是面试重点)

学习是你这个职业一辈子的事 手里有个 1 2 3&#xff0c;不要想着去怼别人的 4 5 6&#xff0c;因为还有你不知道的 7 8 9。保持空瓶心态从 0 开始才能学到 10 全。 毕竟也是跳槽高峰期&#xff0c;我还是为大家准备了这份1500页金三银四宝典&#xff0c;记录的都是真实大厂面…

VS2019安装插件image watch

image watch的作用&#xff1a; &#xff08;1&#xff09;放大、缩小图像&#xff1b; &#xff08;2&#xff09;将图像保存到指定的目录&#xff1b; &#xff08;3&#xff09;显示图像大小、通道数&#xff1b; &#xff08;4&#xff09;拖拽图像&#xff1b; &…

jenkins nginx自动化部署 php项目

在当今快速发展的IT领域&#xff0c;自动化部署已成为提高工作效率和减少错误的关键。Jenkins作为持续集成/持续部署&#xff08;CI/CD&#xff09;的佼佼者&#xff0c;结合Docker容器技术和PHP编程语言&#xff0c;以及Ansible自动化工具&#xff0c;可以实现高效、可靠的自动…

AppFlow无代码轻松搭建模型Agent

随着大语言模型发展至今&#xff0c;如何深度开发和使用模型也有了各种各样的答案&#xff0c;在这些答案当中&#xff0c;Agent无疑是一个热点回答。 通过模型也各种插件的组合&#xff0c;可以让你的模型应用具备各种能力&#xff0c;例如&#xff0c;通过天气查询插件机票查…

使用 SwiftUI 为 macOS 创建类似于 App Store Connect 的选择器

文章目录 前言创建选择器组件使用选择器组件总结前言 最近,我一直在为我的应用开发一个全新的界面,它可以让你查看 TestFlight 上所有可用的构建,并允许你将它们添加到测试群组中。 作为这项工作的一部分,我需要创建一个组件,允许用户从特定构建中添加和删除测试群组。我…

MySQL周内训参照3、简单查询与多表联合复杂查询

基础查询 1、查询用户信息&#xff0c;仅显示用户的姓名与手机号&#xff0c;用中文显示列名。中文显示姓名列与手机号列 SELECT user_id AS 编号, phone AS 电话 FROM user; 2. 根据订购表进行模糊查询&#xff0c;模糊查询需要可以走索引&#xff0c;需要给出explain语句。…

什么是yum源?如何对其进行配置?

哈喽&#xff0c;大家好呀&#xff01;这里是码农后端。今天来聊一聊Linux下的yum源及其配置相关的内容。简单来说&#xff0c;yum源就相当于一个管理软件的工具&#xff0c;可以想象成一个很大的仓库&#xff0c;里面存放着各种我们所需要的软件包及其依赖。 一、Linux下软件包…

【Linux进阶】基础IO函数详解

1.函数open和openat 调用open或openat函数可以打开或创建一个文件。 #include <fcntl.h> int open(const char *path, int ofag, ... /* mode_t mode */);int openat (int fd, const char *path, int oflag, ... /* mode_t mode */); 我们将最后一个参数写为...&#x…

Vue报错:Component name “xxx” should always be multi-word vue/multi-word-component

问题&#xff1a;搭建脚手架时报错&#xff0c;具体错误如下&#xff1a; ERROR in [eslint] E:\personalProject\VueProjects\vueproject2\src\components\Student.vue10:14 error Component name "Student" should always be multi-word vue/multi-word-compon…

windows下以服务方式安装prometheus和grafana

grafana 找到confi下的defaults.ini&#xff0c;找到http_port修改端口号 # The HTTP port to use http_port 3000启动 grafana-server.exe访问localhost:8601即可 下载winsw https://github.com/winsw/winsw 新建grafanaservice.xml <service><id>grafana&…