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

news2024/12/28 2:33:55

写在前面

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

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();
    这就拿到插装后的字节码了

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

写在后面

参考文章列表

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

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

相关文章

visual studio2022配置和使用protobuf

上图证明&#xff0c;我真的测了好多遍&#xff0c;测了好多版本的protobuf&#xff0c;花了很多时间。不过好在最后在vs2022上测通了。 下载protobuf 这里是protobuf下载的地址。 Releases protocolbuffers/protobuf GitHub 个人使用的3.21.9这个版本才跑通的。 1、首先…

【递归、搜索与回溯】floodfill算法二

floodfill算法二 1.被围绕的区域2.太平洋大西洋水流问题3.扫雷游戏4.衣橱整理 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.被围绕的区域…

【OpenSSH】关于操作系统中的自带的SSH你知道怎么用吗

希望文章能给到你启发和灵感&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏 支持一下博主吧&#xff5e; 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境 二、如何开启SSH服务2.1 什么是OpenSSH2.2 对于Windows 10, Windows 11系统2.2.1 如…

bug记录-XFTP传输中文文件夹乱码

bug记录-XFTP传输中文文件夹乱码 今日想往服务器中同步一些脚本,脚本文件夹是中文命名的,用XFTP导入后发现文件夹名称是乱码的 但是在XFTP上显示是正常的 这是因为window默认是GBK编码 linux是UTF-8编码 所以我们只需要把XFTP会话的编码改为UTF-8就可以了 首先点击文件-当前…

【Android面试八股文】Framework面试:Handler怎么进行线程通信的?原理是什么?

文章目录 Handler整体思想Handler工作流程Handler工作流程图总结Handler整体思想 在多线程的应用场景中,将工作线程中需更新 UI 的操作信息 传递到 UI 主线程,从而实现 工作线程对 UI 的更新处理,最终实现异步消息的处理。 Handler工作流程 Handler 机制的工作流程主要包括…

VUE中,table border属性的不同使用方式

在vue中&#xff0c;使用table&#xff0c;在给table设置边框属性时&#xff0c;按照以往的习惯&#xff1a; <table border"1px solid"><table> 这种写法&#xff0c;产生的效果如下&#xff1a;下边、右边的边框明显和上边、左边不同。border属性设置…

【多维动态规划】Leetcode 97. 交错字符串【中等】

交错字符串 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串 子字符串 是字符串中连续的 非空 字符序列。 s s1 s2 … snt…

2024年5月90篇代码大模型论文最全整理

引言&#xff1a; 本文整理 2024 年 5 月发布的 90 篇代码大模型相关论文&#xff0c;其中包括 17 篇发表在今年 ICLR 的论文。根据论文内容&#xff0c;我们将这些论文整理为了基座模型、代码微调、测试基准、代码 Agent、低资源语言处理、AI 代码安全与分析、人机交互、软件…

Lean4Game 开发教程 | 数学形式化

引言 Lean 是一门用于形式化证明的编程语言&#xff0c;它允许严格证明数学定理和验证软件代码的正确性。 本篇介绍 Lean 游戏的编写和发布方式。这类游戏不仅利于对 Lean 本身的学习&#xff0c;对学科知识的理解&#xff0c;还能推动数学圈内人对 Lean 的接触学习。 Lean4…

你不知道的物联网产品有那么多

目录 1. 智能农业设备 ​编辑1.1 土壤感应器 1.2 自动喷灌系统 2. 智能医疗设备 ​编辑2.1 可穿戴健康监测设备 2.2 智能药盒 3. 智能城市基础设施 ​编辑3.1 智能垃圾桶 3.2 智能路灯 4. 智能物流与供应链 ​编辑4.1 货物跟踪设备 4.2 智能仓储系统 5. 智能网关 …

linux server下人脸检测与识别服务程序的系统架构设计

一、绪论 1.1 定义 1.2 研究背景及意义 1.3 相关技术综述 二、人脸检测与识别技术概述 2.1 人脸检测原理与算法 2.2 人脸识别技术及方法 2.3 人脸识别过程简介 三、人脸检测与识别服务程序的系统架构 3.1 系统架构设计 3.2 技术实现流程 四、后续设计及经验瞎谈 4.…

【Mac】Listen 1 for Mac(最强的音乐搜索工具)软件介绍

软件介绍 Listen 1 for Mac 是一款非常方便的音乐播放软件&#xff0c;主要功能是集成多个音乐平台&#xff0c;让用户可以方便地搜索、播放和管理音乐。它是一个用 Python 语言开发的免费开源综合音乐搜索工具项目&#xff0c;最大的亮点在于可以搜索和播放来自网易云音乐&am…

全国公共汽车、出租车拥有情况及客运量、货运量数据

基本信息. 数据名称: 全国公共汽车、出租车拥有情况及客运量、货运量数据 数据格式: Shp、Excel 数据时间: 2020-2022年 数据几何类型: 面 数据坐标系: WGS84 数据来源&#xff1a;中国城市统计年鉴 数据可视化. 2022年全年公共汽车客运总量数据示意图 2022年公路客…

Apifox 更新|定时任务、内网自部署服务器运行接口定时导入、数据库 SSH 隧道连接

Apifox 新版本上线啦&#xff01; 看看本次版本更新主要涵盖的重点内容&#xff0c;有没有你所关注的功能特性&#xff1a; 自动化测试支持设置「定时任务」支持内网自部署服务器运行「定时导入」数据库均支持通过 SSH 隧道连接自动化测试数据库操作优化 1、自动化测试支持设…

计算机语言vs指令vs中央处理器cpu

计算机中如何表示数据 在计算机中&#xff0c;所有数据和指令都是用二进制表示的&#xff0c;即0和1。这些0和1实际上是电压信号的高低电平&#xff0c;0通常表示低电平&#xff08;如0伏特&#xff09;&#xff0c;1表示高电平&#xff08;如5伏特&#xff09;。 指令系统&a…

基本的 Spring Boot 配置步骤和常见的配置项【创建,配置,日志,数据库,安全,MVC】

基本的 Spring Boot 配置步骤和常见的配置项【创建&#xff0c;配置&#xff0c;日志&#xff0c;数据库&#xff0c;安全&#xff0c;MVC】 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、…

UE5基本操作(二)

文章目录 前言相机的移动速度修改默认地图使用初学者内容包文件夹结构 总结 前言 在我们的上一篇文章中&#xff0c;我们已经介绍了一些Unreal Engine 5&#xff08;UE5&#xff09;的基本操作。UE5是一款强大的游戏开发引擎&#xff0c;它提供了许多工具和功能&#xff0c;使…

Elasticsearch 聚合查询

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

SpringMVC处理器映射器HandlerMapping详解

目录 一、前言 二、initHandlerMappings 三、处理器映射器架构 策略接口 请求链 模版类 四、RequestMappingHandlerMapping的初始化 HandlerMethod映射器模版类的初始化 AbstractHandlerMethodMapping.MappingRegistry&#xff1a;内部类注册中心 五、Reques…

AST反混淆|某练习平台混淆代码彻底还原及逆向请求

关注它&#xff0c;不迷路。 本文章中所有内容仅供学习交流&#xff0c;不可用于任何商业用途和非法用途&#xff0c;否则后果自负&#xff0c;如有侵权&#xff0c;请联系作者立即删除&#xff01; 一.目标地址 https://match.yuanrenxue.cn/match/2 二.代码还原 初次请…