当前位置: 首页 > news >正文

Java笔记:使用javassist修改class文件内方法

1.前言

在工作突然有一个需求。线上运维的一个tomcat的web项目,运行的程序不正常。需要修改代码。可是这个项目代码非常的老,并且公司存储的源代码跟线上的不一致。
我了个擦,没有源代码但是还要结局客户的问题。只能到线上将对应程序的class文件拷贝到本地进行修改,每修改一部分就上传到线上覆盖掉之前的class文件,重启tomcat进行测试。(过程想当麻烦)
修改class字节码文件用到 IDEA工具来反编译class进行查看代码,javassist工具进行修改。
修改method中的方法时,主要是对 书写的代码 格式有很多要求

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

2.重要方法介绍

首先需要引入jar包:

<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>

在 Javassist 中,类 Javaassit.CtClass 表示 class 文件。一个 GtClass (编译时类)对象可以处理一个 class 文件,ClassPool是 CtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。
需要注意的是 ClassPool 会在内存中维护所有被它创建过的 CtClass,当 CtClass 数量过多时,会占用大量的内存,API中给出的解决方案是 有意识的调用CtClass的detach()方法以释放内存。

1)ClassPool需要关注的方法:

getDefault : 返回默认的ClassPool 是单例模式的,一般通过该方法创建我们的ClassPool;
appendClassPath, insertClassPath : 将一个ClassPath加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬;
toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class;
get , getCtClass : 根据类路径名获取该类的CtClass对象,用于后续的编辑。

2)CtClass需要关注的方法:

freeze : 冻结一个类,使其不可修改;
isFrozen : 判断一个类是否已被冻结;
prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;
defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法;
detach : 将该class从ClassPool中删除;
writeFile : 根据CtClass生成 .class 文件;
toClass : 通过类加载器加载该CtClass。

上面我们创建一个新的方法使用了CtMethod类。CtMthod代表类中的某个方法,可以通过CtClass提供的API获取或者CtNewMethod新建,通过CtMethod对象可以实现对方法的修改。

3)CtMethod中的一些重要方法:

insertBefore : 在方法的起始位置插入代码;
insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
insertAt : 在指定的位置插入代码;
setBody : 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;
make : 创建一个新的方法。

4)写method的 body代码注意点:

a.如果方法要使用 参数的话 不能直接 使用 。 需要用 $1,$2,$3 来代替。 1-2-3代表前后顺序

列如:
方法上有 (int a,int b) a和b两个参数
那么在 setBody方法中要 使用 a 和 b
就需要用 $1=>a  $2=>b 使用$1,$2来代替 , $0代码的是this$args :$args 指的是方法所有参数的数组类似Object[],需要注意$args[0]对应的是$1,而不是$0$r:指的是方法返回值的类型,主要用在类型的转型上$w:$w代表一个包装类型。主要用在转型上。比如:Integer i = ($w)5;如果该类型不是基本类型,则会忽略$type:返回结果值的类型具体还有很多的符号可以使用,但是不同符号在不同的场景下会有不同的含义,所以在这里就不在赘述,可以看javassist 的说明文档。
http://www.javassist.org/tutorial/tutorial2.html

b.在写入某些对象时要加上包全路径名称

列如: Date , List , Map , 还有一些自定义的pojo类或者 service
// 第三方自己定义的 service 和 pojo 可以在最开始通过
cPool.importPackage("com.gdzy.JZFW.service"); 来进行导入

c.在使用<>这样的泛型定义 标识时 要使用 /* */ 将其包括起来

列如: List<String> 要写成 List/*<String>*/

d.还有一个问题是我需改的class文件需要再重新上线到tomcat中运行,但是我每次修改完运行时都会报一个 线程没有正常结束 的错误,导致tomcat启动不了,最后发现是某些类型的定义不能使用 引用类型 只能 使用 基本类型

列如: Float,Long
不能使用 Float.valueOf() 只能使用 Float.parseFloat() 来进行转换类型

3.示例

1)修改class文件中的某个方法

此方法只能整体修改 method的所有内容。目前没有找到可以局部修改代码的方法。

import java.io.IOException;import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;public class UpdateMethod {private static String pathName = "D:\\Java\\xxxxx\\test\\bin";private static String className = "com.lucumt.Test1";public static void main(String[] args) {updateMethod();}public static void updateMethod(){try {ClassPool cPool = new ClassPool(true);//如果该文件引入了其它类,需要利用类似如下方式声明//cPool.importPackage("java.util.List");//设置class文件的位置cPool.insertClassPath(pathName);// 导入需要引入的 包cPool.importPackage("com.gdzy.JZFW.service");cPool.importPackage("java.text");cPool.importPackage("com.gdzy.JZFW.pojo");cPool.importPackage("com.gdzy.JZFW.util");cPool.importPackage("java.net");cPool.importPackage("java.util");cPool.importPackage("javax.servlet");cPool.importPackage("com.sun.syndication");//获取该class对象CtClass cClass = cPool.get(className);//获取到对应的方法CtMethod cMethod = cClass.getDeclaredMethod("addNumber");//更改该方法的内部实现//需要注意的是对于参数的引用要以$开始,不能直接输入参数名称cMethod.setBody("{ "long z1 = System.currentTimeMillis();\n"+"        System.out.println(\"1111111111111111111111--------------------------------------------------------\");\n"+"        boolean sendOld = false;\n"+"        java.util.Map/*<String, Object>*/ mapparam = new java.util.HashMap();\n" +"        mapparam.put(\"typenameEqual\", \"old_sendEQIM_used\");\n" +"        java.util.List/*<com.gdzy.JZFW.pojo.Useruse>*/ listOldused = this.useruseService.selectList(mapparam);\n"+"        if (listOldused.size() > 0 && ((com.gdzy.JZFW.pojo.Useruse)listOldused.get(0)).getParametervalues().equals(\"1\")) {\n" +"            sendOld = true;\n" +"        }\n"+"        boolean sendNew = false;\n" +"        mapparam.clear();\n" +"        mapparam.put(\"typenameEqual\", \"new_sendEQIM_used\");\n" +"        System.out.println(\"222222222222222222222222-----------------------------------\");\n" +"        java.util.List/*<com.gdzy.JZFW.pojo.Useruse>*/ listNewused = this.useruseService.selectList(mapparam);\n" +"        if (listNewused.size() > 0 && ((com.gdzy.JZFW.pojo.Useruse)listNewused.get(0)).getParametervalues().equals(\"1\")) {\n" +"            sendNew = true;\n" +"        }\n"+ "............." }");//替换原有的文件cClass.writeFile(pathName);System.out.println("=======change finish=========");} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}

2)在Class文件中增加方法

利用Javassist增加方法比修改方法更简单,先将要新增的方法内容赋值到字符串,然后分别调用相关类的 make 和 addMethod 方法即可 。

public static void addMethod(){
try {
ClassPool cPool = new ClassPool(true);
cPool.insertClassPath(pathName);
CtClass cClass = cPool.get(className);CtMethod cMethod = cClass.getDeclaredMethod("addNumber");//增加一个新方法String methodStr ="public void showParameters(int a,int b){"+" System.out.println(\"First parameter: \"+a);"+" System.out.println(\"Second parameter: \"+b);"+"}";CtMethod newMethod = CtNewMethod.make(methodStr, cClass);cClass.addMethod(newMethod);//调用新增的方法cMethod.setBody("{ showParameters($1,$2);return $1*$1*$1+$2*$2*$2; }");cClass.writeFile(pathName);} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

3)在Class文件中增加成员变量

public static void addField(){
try {
ClassPool cPool = new ClassPool(true);
cPool.insertClassPath(pathName);
CtClass cClass = cPool.get(className);//增加一个新成员变量cClass.addField(CtField.make("private String str;",cClass));cClass.writeFile(pathName);} catch (NotFoundException e) {e.printStackTrace();} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}

相关文章:

Java笔记:使用javassist修改class文件内方法

1.前言 在工作突然有一个需求。线上运维的一个tomcat的web项目&#xff0c;运行的程序不正常。需要修改代码。可是这个项目代码非常的老&#xff0c;并且公司存储的源代码跟线上的不一致。 我了个擦&#xff0c;没有源代码但是还要结局客户的问题。只能到线上将对应程序的clas…...

华为云云耀云服务器L实例评测 |云服务器性能评测

通过上一篇文章华为云云耀云服务器 L 实例评测 &#xff5c;云服务器选购&#xff0c;我已经购买了一台 Centos 系统的云耀云服务器 L 实例。 在获得云耀云服务器 L 实例后&#xff0c;首要任务是熟悉云耀云服务器 L 实例的性能&#xff0c;对云耀云服务器 L 实例的性能进行测…...

iphone的safari浏览器实现全屏的pwa模式,并修改顶部状态栏背景颜色

要想修改顶部背景颜色&#xff0c;需要用到这个属性&#xff1a;content就是你要设置的颜色 <!-- 状态栏的背景色 --><meta name"theme-color" content"#f8f8f8" /> 然后再加上下面的设置&#xff1a; <!-- 网站开启对 web app 程序的支持…...

springboot对接rabbitmq并且实现动态创建队列和消费

背景 1、对接多个节点上的MQ&#xff08;如master-MQ&#xff0c;slave-MQ&#xff09;&#xff0c;若读者需要自己模拟出两个MQ&#xff0c;可以部署多个VM然后参考 docker 安装rabbitmq_Steven-Russell的博客-CSDN博客 2、队列名称不是固定的&#xff0c;需要接受外部参数&…...

Spring的后处理器-BeanFactoryPostprocessor

目录 Spring后处理器 Bean工厂后处理器-BeanFactoryPostProcessor 修改beanDefinition对象 添加beanDefiniton对象 方法一 方法二 自定义Component Spring后处理器 Spring后处理器是Spring对外开放的重要拓展点&#xff08;让我们可以用添加自己的逻辑&#xff09;&…...

Flutter 必备知识点

Flutter 升级 确保在项目根目录下&#xff08;含有 pubspec.yaml 的文件夹&#xff09; 在命令行中输入命令&#xff1a; flutter channel输出&#xff1a; Flutter channels: * mastermainbetastable这个可以在 pubspec.yaml 中查看&#xff1a; 切换分支也很简单&#xf…...

什么是FMEA(失效模式和影响分析)?

失效模式和影响分析&#xff08;FMEA&#xff09;是一个在开发阶段&#xff0c;用于确定产品或流程可能的风险和失败点的有条理的过程。FMEA团队会研究失效模式&#xff0c;也就是产品或流程中可能出错的地方&#xff0c;以及这些失效可能带来的影响&#xff08;如风险、损害、…...

Redis面试题(三)

文章目录 前言一、怎么理解 Redis 事务&#xff1f;二、Redis 事务相关的命令有哪几个&#xff1f;三、Redis key 的过期时间和永久有效分别怎么设置&#xff1f;四、Redis 如何做内存优化&#xff1f;五、Redis 回收进程如何工作的&#xff1f;六、 加锁机制总结 前言 怎么理…...

Python错误处理指南:优雅应对异常情况

目录 一. 异常是什么&#xff1f;二. 使用 try 和 except三. 捕获多个异常四. 使用 else五. 使用 finally六. 自定义异常七.Python中常见异常处理类型八.Python中常见异常处理实例九.异常处理最佳实践十.结论 当编写Python代码时&#xff0c;错误处理是一个重要的方面&#xff…...

MySQL学习笔记12

MySQL 查询语句&#xff1a; 1、查询五子句&#xff1a;&#xff08;重点&#xff09; mysql> select */字段列表 from 数据表名称 where 子句 group by 子句 having 子句 order by 子句 limit 子句; 1&#xff09;where 子句&#xff1b;条件筛选。 2&#xff09;group…...

【owt】构建m79的owt-client-native:使用vs2017

家里电脑换成了台式机,拷贝代码发现了三年前的owt客户端mfc工程。 不用下载第三方库,试着构建下: owt-client-native 我这里有3年前的代码,思索了下还是用vs2017构建吧: 重新构建一下 选用x86 的 vs2017 vs的命令行控制台 cls可以清理屏幕 之前构建过vs2022的webrtc原版 …...

Cpp/Qt-day020918Qt

目录 完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两…...

Spring面试题10:Spring的XMLBeanFactory怎么使用

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring的XMLBeanFactory怎么使用 XmlBeanFactory是Spring框架中的一个实现类,它是BeanFactory接口的一个具体实现。XmlBeanFactory的主要作用是通…...

自定义数据类型

前言&#xff1a;小伙伴们又见面啦&#xff0c;今天这篇文章&#xff0c;我们来谈谈几种自定义数据类型。 目录 一.都有哪些自定义数据类型 二.结构体 结构体内存对齐 1.如何对齐 2.为什么要对齐 3.节省空间和提升效率的方法 &#xff08;1&#xff09;让占用空间小的成员…...

产品团队的需求验证和确认

需求核实过程是确保软件满足特定的规格要求&#xff0c;而验证则侧重于软件是否达到了最终用户的期望和需求。 如果你正在开发一种医疗产品&#xff0c;这种区别也可能在法规和标准中有所体现&#xff0c;例如&#xff1a; 820.30(f)&#xff1a;设计验证应确认设计的成果符合…...

【JVM】类加载的过程

文章目录 类的生命周期加载验证准备解析初始化简要概括 类的生命周期 一个类型从被加载到虚拟机内存中开始&#xff0c;到卸载出内存为止&#xff0c;它的整个生命周期将会经历加载 &#xff08;Loading&#xff09;、验证&#xff08;Verification&#xff09;、准备&#xf…...

Golang 结构化日志包 log/slog 详解(四):分组、上下文和属性值类型

上一篇文章讲解了 log/slog 包中的自定义日志属性字段和日志级别&#xff0c;本文讲解下分组、上下文和属性值类型 分组输出 slog 支持将字段放在组中并且可以给分组指定名称。如何展示分组的内容&#xff0c;取决于使用的 handler&#xff0c;例如 TextHandler 使用点号分隔…...

小白学Python:提取Word中的所有图片,只需要1行代码

#python# 大家好&#xff0c;这里是程序员晚枫&#xff0c;全网同名。 最近在小破站账号&#xff1a;Python自动化办公社区更新一套课程&#xff1a;给小白的《50讲Python自动化办公》 在课程群里&#xff0c;看到学员自己开发了一个功能&#xff1a;从word里提取图片。这个…...

pip修改位于用户目录下的缓存目录

默认 pip 缓存目录&#xff1a; Windows: C:\Users\${用户名}\AppData\Local\pip\cache Linux: ~/.cache/pip 一、修改方式 1.命令方式 pip config set global.cache-dir "D:\kwok\data\pip-cache" 2.配置文件方式 ① Windows&#xff1a; C:\Users\${用…...

更新、修改

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 语法: update 表名 列名该列新值, 列名该列新值, ... where 记录匹配条件; 说明&#xff1a;update 更新、修改 set 设置 …...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中&#xff0c;网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时&#xff0c;开发者迫切需要一套高效、可靠且跨平台的调试方案。过去&#xff0c;我们或多或少使用过 Chrome DevTools、Remote Debug…...

Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么&#xff1f;它的作用是什么&#xff1f; Spring框架的核心容器是IoC&#xff08;控制反转&#xff09;容器。它的主要作用是管理对…...