javassist

2022/07/20

前言

ASM太抽象了,还是Javassist友好。

介绍

没啥说的,一个处理Java字节码的类库,不需要我们懂字节码规范,直接操作。

结构模型

image-20230314162300648

生成类

1.获取默认类池

ClassPool classPool = ClassPool.getDefault();

2.创建一个自定义类

CtClass ctClass = classPool.makeClass();

3.添加东西

4.写入磁盘

这里写入磁盘可以用如下两种方法

package org.example;

import javassist.*;
import javassist.bytecode.AccessFlag;

import java.io.File;
import java.io.FileOutputStream;

public class JavassistDemo {
    public static void main(String[] args) {

        JavassistDemo a = new JavassistDemo();
        a.makeClass();

    }

    public void makeClass(){
        //获取默认类池
        ClassPool classPool = ClassPool.getDefault();
        //创建一个类ClassDemo
        CtClass ctClass = classPool.makeClass("javassisttest.ClassDemo");
        //让该类实现序列化接口
        ctClass.setInterfaces(new CtClass[]{classPool.makeInterface("java.io.Serializable")});
        try {
            //新建一个int类型名为id的成员变量
            CtField id = new CtField(CtClass.intType, "id", ctClass);
            //将id设置为public
            id.setModifiers(AccessFlag.PUBLIC);
            //将该id属性"赋值"给ClassDemo
            ctClass.addField(id);

            //添加无参构造方法
            CtConstructor ctConstructor = CtNewConstructor.make("public ClassDemo(){};", ctClass);
            ctClass.addConstructor(ctConstructor);

            //添加有参构造方法
            CtConstructor ctConstructor1 = CtNewConstructor.make("public ClassDemo(int id){this.id = id;}", ctClass);
            ctClass.addConstructor(ctConstructor1);

            //添加普通方法1
            CtMethod ctMethod = CtNewMethod.make("public void calcDemo(){java.lang.Runtime.getRuntime().exec(\"Calc.exe\");}", ctClass);
            ctClass.addMethod(ctMethod);

            //添加普通方法2
            CtMethod ctMethod1 = CtNewMethod.make("public void hello(){System.out.println(\"Hello Javassist!!!\");}", ctClass);
            ctClass.addMethod(ctMethod1);

            //将class文件写入磁盘
            //转换成字节流
            byte[] bytes = ctClass.toBytecode();
            //写入磁盘
            File classPath = new File(new File(System.getProperty("user.dir"), "/src/main/java/org/example/"), "ClassDemo.class");
            FileOutputStream fos = new FileOutputStream(classPath);
            fos.write(bytes);
            fos.close();

            //验证-调用方法
            //注意这里可能会抛javassist.CannotCompileException异常因为同个 Class 是不能在同个 ClassLoader 中加载两次的,所以在输出 CtClass 的时候需要注意下
            //需要通过一个未加载该class的classloader加载即可,为此javassist内置了一个classloader

            //获取javassist的classloader
            ClassLoader loader = new Loader(classPool);
            System.out.println("loading");
            //通过该classloader加载才是新的一个class
            Class<?> clazz = loader.loadClass("javassisttest.ClassDemo");

            //反射调用hello
            clazz.getDeclaredMethod("hello").invoke(clazz.newInstance());
            //反射调用calc
            clazz.getDeclaredMethod("calcDemo").invoke(clazz.newInstance());

        } catch (Exception e){
            System.out.println(e);
        }
    }
}

image-20230314165729925

动态获取类方法并修改

Demo1.java:被修改的类

package org.example.Demo;

public class Demo1 {
    public void hello() {
        System.out.println("hello calc!!!");
    }
}

Demo2.java:修改了Demo1hello方法

package org.example.Demo;

import javassist.*;


public class Demo2 {

    public void GetMethod() throws Exception {
        //获取默认类池
        ClassPool classPool = ClassPool.getDefault();
        //获取目标类
        CtClass cc = classPool.get("org.example.Demo.Demo1");
        //获取类的方法
        CtMethod m = cc.getDeclaredMethod("hello");
        //插入命令执行代码
        m.insertBefore("{java.lang.Runtime.getRuntime().exec(\"Calc.exe\");}");
        //转换为class对象
        Class c = cc.toClass();
        //反射调用对象
        Demo1 D = (Demo1) c.newInstance();
        //执行方法
        D.hello();
    }
    public static void main(String[] args) throws Exception {
        Demo2 D2 = new Demo2();
        D2.GetMethod();
    }

}

image-20230314173021873

参考文档

官网: http://www.javassist.org/

关于Java字节码编程javassist的详细介绍 | w3c笔记 (w3cschool.cn)