Java核心: 使用instrumentation

news2024/11/17 9:50:42

在上一篇Java核心: 注解处理器我们提到,通过实现AbstractProcessor,并调用javac -processor能够生成代码来实现特殊逻辑。不过它存在两个明显的问题:

  1. 只能新增源文件来扩展逻辑,无法修改现有的类或方法
  2. 必须有一个单独的编译过程,调用javac -processor或者用Maven的annotationProcessor,不适用于已经编译好的jar

Java核心: 使用asm操作字节码讲了怎么使用asm修改类的字节码,解决了上面的第1个问题。不过即使用ASM,仍然需要自己创建ClassReader读取class文件,自定义ClassLoader加载字节码,虽然不需要单独编译,但是这些操作是需要额外做的。

instrumentation提供的能力是可以注册字节码转换器,在类加载时先将字节码传递给字节码转换器,转换之后才被真正的加载。使用者来并不感知这个过程,只是在启动程序的命令中添加一个-javaagent参数即可,应该说使用体验还是不错的。

java -javaagent:AsmToString.jar=hoho -cp ./swing-1.0-SNAPSHOT.jar  com.keyniu.asm.diagnostic.DiagnosticMain

1. 定义和使用

接下来我们要做的是定义一个最简单的instrumentation实现,仅仅是打印-javaagent后带的参数,以及执行过程加载的所有的类、它所属的ClassLoader。首先要做的是创建工程,后续用于打包成agent的jar,这里我们命名为AsmToString.jar。这里使用mvn archetype:generate创建工程

mvn archetype:generate  \
  -DarchetypeGroupId=org.apache.maven.archetypes \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.4 \
  -DgroupId=com.keyniu.agent \
  -DartifactId=AsmToString \
  -Dversion=0.1 \
  -Dpackage=com.keyniu.agent \
  -DinteractiveMode=false
1. 实现Premain

现在我们可以在工程下定义我们的Premain类ToStringGenerator,这个类的核心是实现public static void premain(final String arg, Instrumentation ins)方法,这里我们只打印参数arg,以及传递给字节码转换器的信息。使用Instrumentation.addTransformer注册字节码转换器ClassFileTransformer实现类。

package org.keyniu.agent;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class ToStringGenerator {

    public static void premain(final String arg, Instrumentation ins) {
        System.out.println("arg: " + arg);
        ins.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                System.out.println("ClassLoader: " + loader.getName());
                System.out.println("className: " + className);
                System.out.println("classBeingRedefined: " + classBeingRedefined);
                return classfileBuffer;
            }
        });
    }
}

在继续之前我们来仔细看看ClassFileTransformer.tranform的定义,它有5个参数

参数

说明

举例

loader

加载当前要进行字节码转换的类的类加载器

app

className

当前要进行字节码转换的类的类名

org/objectweb/asm/util/TraceRecordComponentVisitor

classBeingRefefined

被重定义(Instrumentation.redefineClasses)的类的Class对象,否则是null

null

protectionDomain

目标类的安全权限

null

classfileBuffer

转换之前的字节码

字节码数组

2. 定义MANIFEST.MF

为了让-javaagent能使用这个jar,需要在MANIFEST.MF里添加Premain-Class的定义。在Maven的build -> plugins -> plugin配置maven-jar-plugin。

<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                <addClasspath>true</addClasspath>
            </manifest>
            <manifestEntries>
                <Premain-Class>org.keyniu.agent.ToStringGenerator</Premain-Class>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

这里我们重点关注和的配置,我们看一下每个配置的作用。addDefaultImplementationEntries用于在MANIFEST.MF文件中生成

Implementation-Title: AsmToString
Implementation-Version: 1.0-SNAPSHOT

addDefaultSpecificationEntries会在MANIFEST.MF里记录Specification相关信息

Specification-Title: AsmToString
Specification-Version: 1.0

addClassPath则会在MANIFEST.MF文件中添加一行Class-Path

Class-Path: lib/dependency1.jar lib/dependency2.jar

manifestEntries用于添加我们自定义的属性,这里是添加Premain-Class属性以及它对应的类信息。现在可以通过mvn package打包了。

3. 使用javaagent

在拿到agent的jar包之后,就可以在另外一个工程里使用这个jar包了。复用在Java核心: 使用asm操作字节码里定义的类Account,代码如下,只是定义了一个remain字段、transfer方法。

package com.keyniu.asm;
import com.keyniu.asm.diagnostic.Diagnostic;
import com.keyniu.asm.lombok.ToString;
@ToString
public class Account {
    private int remain = 99;
    @Diagnostic
    public void transfer(String sb, int amount) {
        System.out.println("transfer to " + sb + " amount: " + amount);
        try {
            Thread.sleep((int) (Math.random() * 1000));
        } catch (InterruptedException e) {
        }
    }
}

用来测试的maif方法也极其简单,仅仅是创建Account实例,打印toString方法的输出,因为我们并没有定义toString方法,所以输出是Object默认的com.keyniu.asm.Account@3feba861

package com.keyniu.testagent;
import com.keyniu.asm.Account;
public class TestAgent {

    public static void main(String[] args) {
        Account account = new Account();
        System.out.println(account.toString());
    }
}

现在可以使用-javaagent来测试执行了,我们看看执行的命令以及返回的输出

java -javaagent:D:/Workspace/HelloJava17/AsmToString-1.0-SNAPSHOT.jar=hoho -classpath ./swing-1.0-SNAPSHOT.jar  com.keyniu.testagent.TestAgent

2. 实战: 生成toString方法

上一节中我们创建了一个javaagent的jar,整个流程已经跑通了,只是这个agent本身并没有什么实际意义,仅仅是打印我们加载的所有类而已。现在我们让他变得有用一点,为注解了@ToString的类生成toString方法,案例中Account类标注了@ToString,关于ToString的代码,asm的更详细介绍,可以看Java核心: 使用asm操作字节码。这里我们默认读者是会asm的基本操作的。

1.引入asm依赖

首先要做的是在AsmToString工程里引入asm类库的依赖。

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.7</version> <!-- 使用时请检查是否有更新的版本 -->
</dependency>

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-util</artifactId>
    <version>9.7</version> <!-- 使用时请检查是否有更新的版本 -->
</dependency>
2. 定义ToStringClassVisitor

代码完全复用Java核心: 使用asm操作字节码里的ToStringClassVisitor类,代码如下

package org.keyniu.agent;


import org.objectweb.asm.*;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

public class ToStringClassVisitor extends ClassVisitor {

    private boolean isAnnotated = false;
    private Map<String, String> fields = new LinkedHashMap<>();
    private String className;

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.className = name;
        super.visit(version, access, name, signature, superName, interfaces);
    }

    protected ToStringClassVisitor(ClassVisitor classVisitor) { // classVisitor接收一个ClassWriter用于生成字节码
        super(Opcodes.ASM9, classVisitor);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        if ("Lcom/keyniu/asm/lombok/ToString;".equals(descriptor)) {
            isAnnotated = true;
        }
        return super.visitAnnotation(descriptor, visible);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        fields.put(name, descriptor);
        return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
        if (isAnnotated && fields.size() > 0) {
            MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
            mv.visitCode();
            // 创建StringBuilder并调用构造函数
            mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
            mv.visitInsn(Opcodes.DUP);
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);

            // 拼接类名,比如 Account(
            mv.visitLdcInsn(className.substring(className.lastIndexOf("/") + 1) + "(");
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

            Iterator<Map.Entry<String, String>> it = fields.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, String> kv = it.next();
                String fieldName = kv.getKey();
                String fieldDesc = kv.getValue();
                mv.visitVarInsn(Opcodes.ALOAD, 0); // 载入this
                mv.visitFieldInsn(Opcodes.GETFIELD, className, fieldName, fieldDesc);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(" + fieldDesc + ")Ljava/lang/StringBuilder;", false);
                if (it.hasNext()) {
                    mv.visitLdcInsn(",");
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
                }
            }

            // 拼接右括号,Account(字段=值)
            mv.visitLdcInsn(")");
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);

            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);

            mv.visitInsn(Opcodes.ARETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();

        }
        super.visitEnd();
    }
}
3.修改ToStringGenerator实现
package org.keyniu.agent;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class ToStringGenerator {

    public static void premain(final String arg, Instrumentation ins) {
        System.out.println("arg: " + arg);
        ins.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader load, String clazz, Class<?> redefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
                ClassReader cr = new ClassReader(classfileBuffer);
                // 写字节码
                ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                ClassVisitor cv = new ToStringClassVisitor(cw);
                // 跑事件流
                cr.accept(cv, ClassWriter.COMPUTE_FRAMES); // 0表示不计算最大堆栈大小和最大局部变量表大小
                // 获取修改后的字节码并写入到文件
                byte[] transformedBytes = cw.toByteArray();
                return transformedBytes;
            }
        });
    }
}
4. 查看运行输出

我们将AsmToString的jar拷到D:盘下,使用如下命令执行TestAgent,我们看到Accout的toString已经生成了并打印Account类中的字段。

java -javaagent:D:/AsmToString-1.0-SNAPSHOT.jar=hoho -classpath ./swing-1.0-SNAPSHOT.jar  com.keyniu.testagent.TestAgent

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

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

相关文章

【stm32/CubeMX、HAL库】嵌入式实验六:定时器(1)|定时器中断

参考&#xff1a; 【【正点原子】手把手教你学STM32CubeIDE开发】 https://www.bilibili.com/video/BV1Wp42127Cx/?p13&share_sourcecopy_web&vd_source9332b8fc5ea8d349a54c3989f6189fd3 《嵌入式系统基础与实践》刘黎明等编著&#xff0c;第九章定时器。 实验内容…

【VRIO模型】用战略模型找到你的「核心竞争力」

一个项目或企业要获得成功&#xff0c;其所拥有的资源和资源发挥的效能至关重要。那么&#xff0c;什么样的资源才能支撑项目发展的核心竞争力呢&#xff1f;VRIO是针对组织内部资源与能力&#xff0c;分析竞争优势和弱点的工具。 VRIO模型 VRIO模型是针对企业内部资源与能力…

LeetCode刷题之HOT100之三数之和

2024/5/29 天气真他妈好呀&#xff01;好天气让我舒畅&#xff0c;可惜俺今天把我养护了两三个月的栀子花花苞又整掉了一朵&#xff0c;呜呜呜&#xff01;看着它们一天天长大&#xff0c;花苞边由嫩绿泛白&#xff0c;结果被我不小心弄掉了&#xff0c;我是罪人。它们还想&…

SunoAI 智能创作歌曲神器的源码系统架构 前后端分离 附带完整的源代码包以及安装搭建教程

系统概述 SunoAI 智能创作歌曲神器的源码架构是基于先进的技术理念构建而成。前端采用了现代化的用户界面设计&#xff0c;为用户提供直观、友好的操作环境。后端则运用高效的算法和强大的处理能力&#xff0c;确保歌曲创作的准确性和高质量。 代码示例 系统特色功能模块 1.智…

Vue3 - 实现一个雨水滴落的动画效果

在 Vue 3 中实现一个雨水滴落的动画效果,可以使用 HTML5 的 <canvas> 元素和 JavaScript 来绘制和控制动画。 以下是一个实现雨水滴落效果的示例: 创建一个 Vue 3 项目 首先,确保你已经创建了一个 Vue 3 项目。如果还没有,可以使用 Vue CLI 来创建: vue create r…

如果不花钱,又担心钱存着贬值,怎么办?如果把大部分钱投入到赚钱的事上,特别是自己专业上,比如说我是十年的程序员,帮我分析投资?

如果你不想花钱&#xff0c;但又担心钱存着会贬值&#xff0c;有几个策略可以帮助你在不花费大量金钱的情况下保护你的资金价值&#xff1a; 高收益储蓄账户或货币市场账户&#xff1a; 选择一个高收益的储蓄账户或货币市场账户&#xff0c;这些账户通常比传统储蓄账户提供更高…

[ACL 2024 Main] StickerConv: 从零开始的多模态共情回复生成

文章目录 &#x1f4c3; Abstract&#x1f52d; Contributions&#x1f50d; Approach✨ Agent for StickerConv✨ StickerConv Dataset✨ PEGS✨ 多模态共情回复评价 &#x1f4ca; Experiments&#x1f4c4; Conclusion&#x1f310; 开源网址 Project Page: StickerConv Pro…

【云原生】kubernetes中Configmap原理解析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

汽车IVI中控开发入门及进阶(二十一):DAB和FM 收音机

前言: 在过去的十年里,数字收音机对车载娱乐产生了重大影响。现在,几乎每辆新车都标配了这项技术,这也是我们60%以上的人收听收音机的方式。甚至有传言称,在不久的将来,将永久关闭调频发射机,使许多车载收音机过时。但一些相对年轻的汽车在工厂里仍然没有安装DAB,而且…

Linux共享内存创建和删除

最近项目中使用到了共享内存记录下 创建共享内存: 删除共享内存: 代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <u…

服务器数据恢复—异常断电导致ESXi虚拟机无法启动的数据恢复案例

服务器数据恢复环境&#xff1a; 某大厂PS4000服务器&#xff0c;服务器上部署VMware ESXi虚拟化平台。 服务器故障&#xff1a; 机房断电&#xff0c;重启后服务器中的某台虚拟机不能正常启动。管理员查看虚拟机配置文件&#xff0c;发现无法启动的虚拟机的配置文件除了磁盘文…

【全开源】自习室在线订座小程序源码(FastAdmin+ThinkPHP+uView)

打造高效学习空间的必备工具 一、引言&#xff1a;自习室订座难题的解决之道 在如今的学习环境中&#xff0c;自习室成为了学生们备战考试、进行深度学习的重要场所。然而&#xff0c;随着学生人数的增加&#xff0c;自习室座位资源变得日益紧张。为了解决这一难题&#xff0…

SSL证书该如何选择,DV、OV、EV又有什么区别?

选择SSL证书时&#xff0c;您需要根据您的网站性质、安全需求以及预算来决定。DV&#xff08;域名验证&#xff09;、OV&#xff08;组织验证&#xff09;、EV&#xff08;扩展验证&#xff09;是三种不同类型的SSL证书&#xff0c;它们在验证深度、安全性和可见性方面有所差异…

Fastjson 反序列化漏洞[1.2.24-rce]

漏洞复现环境搭建请参考 http://t.csdnimg.cn/vSaaw kali切换jdk版本请参考 Kali安装JAVA8和切换JDK版本的详细过程_kali安装jdk8-CSDN博客 漏洞原理 Fastjson提供的com.sun.rowset.JdbcRowSetImpl类下的dataSourceName方法支持传入一个RMI/LDAP源&#xff0c;支持远程调用。…

走进智慧仓储:3D可视化工厂园区革新物流新纪元

在快节奏的现代生活中&#xff0c;物流仓储行业扮演着至关重要的角色。随着科技的飞速发展&#xff0c;传统仓储模式正面临一场前所未有的变革。今天&#xff0c;就让我们一起看看3D可视化技术如何为物流行业带来前所未有的便利与效率。 什么是3D可视化工厂园区&#xff1f; 3…

ABP框架+Mysql(二)

展示页面--图书列表页面 本地化 开始的UI开发之前,我们首先要准备本地化的文本(这是你通常在开发应用程序时需要做的).本地化文本在前端页面会常用。 本地化文本位于 Acme.BookStore.Domain.Shared 项目的 Localization/BookStore 文件夹下: 打开 en.json (英文翻译)文件并更…

「浏览器」跨站请求伪造CSRF攻击的原理以及防范措施

前言 HTTP 是一个无状态的协议&#xff0c;比如需要账号密码登录的网站这个场景&#xff0c;为了避免每次都需要重复输入&#xff0c;有一种方案就是Cookie&#xff0c;具体使用不做赘述&#xff0c;但是这样带来了一些安全问题。跨站请求伪造&#xff08;CSRF&#xff09;攻击…

贵州省特岗教师报名流程及一寸蓝底证件照电子版制作指南

贵阳市2024年特岗教师招聘公告已发布&#xff0c;310个岗位虚位以待&#xff0c;报名工作将于5月27日9:00至5月29日18:00期间进行。本文将为您提供详细的报名流程以及如何制作符合要求的一寸蓝底证件照电子版&#xff0c;助您顺利报名。 一、贵州特岗教师网上报名流程 网上报…

EQMentor情商导师文心智能体:引领情商提升与人际关系改善的智能导师

目录 一、引言 情商的重要性 EQMentor智能体的诞生背景与目的 二、EQMentor智能体的概述 EQMentor智能体 简述EQMentor情商智能体的核心功能与特点 三、 EQMentor情商导师智能体 智能体的设计理念 智能体的功能特点 智能体的使用举例 四、结语 一、引言 情商的重要…

六面体大米装袋机在提升大米包装效率中的作用

在当今社会&#xff0c;随着科技的飞速发展&#xff0c;各行各业都在寻求创新与突破&#xff0c;以提升生产效率和降低成本。而在大米包装领域&#xff0c;六面体大米装袋机的出现&#xff0c;无疑为整个行业带来了革命性的变化。这种先进的机械设备不仅提高了大米的包装效率&a…