使⽤javassist修改class⽂件内⽅法
使⽤javassist修改class⽂件内⽅法
在⼯作突然有⼀个需求。线上运维的⼀个tomcat的web项⽬,运⾏的程序不正常。需要修改代码。可是这个项⽬代码⾮常的⽼,并且公司存储的源代码跟线上的不⼀致。
我了个擦,没有源代码但是还要结局客户的问题。只能到线上将对应程序的class⽂件拷贝到本地进⾏修改,每修改⼀部分就上传到线上覆盖掉之前的class⽂件,重启tomcat进⾏测试。(过程想当⿇烦)
修改class字节码⽂件⽤到 IDEA⼯具来反编译class进⾏查看代码,javassist⼯具进⾏修改。
修改method中的⽅法时,主要是对 书写的代码 格式有很多要求
javasssist的主要功能
前⾔
Javassist是⽇本⼈开发的⼀款编辑class字节码框架,可以⽤来检查、动态修改及创建Java类。与JDK⾃带的反射功能相⽐Javassist 功能更加强⼤,熟练使⽤Javassist⼯具对提⾼Java动态编程有着重要意义。
功能
官⽹地址:
主要的类:
ClassPool // 操作前都要创建⼀个 class痴
CtClass // 具体的操作class的对象
养生保健小知识CtMethod // class⽂件中的 某个⽅法对象
CtField // class⽂件中的 某个⽅法字段
主要的⽅法
CtClass.addMethod
CtClass.writeFile // 写⼊⽂件,
CtClass.addField
CtMethod.insertBefore
CtMethod.insertAfter
CtMethod.insertAt
CtMethod.setBody
简单使⽤
这些只是⼀些简单的使⽤⽅法,因为某些没有源代码,⽽要修改⼀⼩部分线上class代码时,就可以使⽤。更⾼深的动态编程 。 俺不会,(怎么java的东西这么多,感觉每个都好深,好复杂。555~)
修改class⽂件中的某个⽅法
此⽅法只能整体修改 method的所有内容。⽬前没有到可以局部修改代码的⽅法。
先下载jar包/或导⼊maven
<!-- mvnrepository/artifact/org.javassist/javassist -->
<!-- mvnrepository/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
写⼀个 main⽅法,操作对应 class⽂件
package com.lucumt;
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("");
cPool.importPackage("com.gdzy.JZFW.pojo");
好医保免费医疗金最高能领多少cPool.importPackage("com.gdzy.JZFW.util");
cPool.importPackage("java");
cPool.importPackage("java.util");
cPool.importPackage("javax.servlet");
cPool.importPackage("com.sun.syndication");
//获取该class对象
CtClass cClass = (className);
//获取到对应的⽅法
CtMethod cMethod = DeclaredMethod("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.(0)).getParametervalues().equals(\"1\")) {\n"+ " sendOld = true;\n"+
" }\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.(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();
}
}
}
写method的 body代码注意点:
1. 如果⽅法要使⽤ 参数的话 不能直接 使⽤ 。 需要⽤ $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:返回结果值的类型
2. 在写⼊某些对象时要加上包全路径名称
列如: Date , List , Map ,还有⼀些⾃定义的pojo类或者 service
// 第三⽅⾃⼰定义的 service 和 pojo 可以在最开始通过
cPool.importPackage("com.gdzy.JZFW.service"); 来进⾏导⼊
3. 在使⽤<>这样的泛型定义 标识时 要使⽤ /* */ 将其包括起来
列如: List<String> 要写成 List/*<String>*/
4. 还有⼀个问题是我需改的class⽂件需要再重新上线到tomcat中运⾏,但是我每次修改完运⾏时都会报⼀个 线程没有正常结束 的错
误,导致tomcat启动不了,最后发现是某些类型的定义不能使⽤ 引⽤类型 只能 使⽤ 基本类型
列如: Float,Long
不能使⽤ Float.valueOf() 只能使⽤ Float.parseFloat() 来进⾏转换类型
在Class⽂件中增加⽅法
利⽤Javassist增加⽅法⽐修改⽅法更简单,先将要新增的⽅法内容赋值到字符串,然后分别调⽤相关类的 make 和 addMethod ⽅法即可 。
public static void addMethod(){
try{
ClassPool cPool =new ClassPool(true);
cPool.insertClassPath(pathName);
CtClass cClass = (className);
CtMethod cMethod = DeclaredMethod("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();
}
}
在Class⽂件中增加成员变量
public static void addField(){
try{
ClassPool cPool =new ClassPool(true);
cPool.insertClassPath(pathName);
CtClass cClass = (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();
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论