使用javassist修改class文件内方法

使用javassist修改class文件内方法

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

javasssist的主要功能

前言
  • Javassist是日本人开发的一款编辑class字节码框架,可以用来检查、动态修改及创建Java类。与JDK自带的反射功能相比Javassist功能更加强大,熟练使用Javassist工具对提高Java动态编程有着重要意义。
功能
  • 官网地址: javassist官网

  • 主要的类:

    • ClassPool // 操作前都要创建一个 class痴
    • CtClass // 具体的操作class的对象
    • CtMethod // class文件中的 某个方法对象
    • CtField // class文件中的 某个方法字段
  • 主要的方法

    • CtClass.addMethod
    • CtClass.removeMethod
    • CtClass.removeField
    • CtClass.writeFile // 写入文件,
    • CtClass.addField
    • CtMethod.insertBefore
    • CtMethod.insertAfter
    • CtMethod.insertAt
    • CtMethod.setBody

简单使用

  • 这些只是一些简单的使用方法,因为某些没有源代码,而要修改一小部分线上class代码时,就可以使用。更高深的动态编程 。 俺不会,(怎么java的东西这么多,感觉每个都好深,好复杂。555~)

修改class文件中的某个方法

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

  • 先下载jar包/或导入maven

  • <!-- https://mvnrepository.com/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("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();
            }
        }
    
    }
    

写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增加方法比修改方法更简单,先将要新增的方法内容赋值到字符串,然后分别调用相关类的 makeaddMethod 方法即可 。

    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();
            }
        }
    

在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();
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值