Android编译时技术(一)Javassist 基础使用

一、前言

        所谓的Javassist,其实就是如何生成一个Class文件或者修改一个Class文件的工具,包括对Class里的成员变量或者方法进行增加或修改。相比于ASM,Javassist最大的好处就是方便,简单,不用去关心字节码操作。

二、用Javassist生成文件

        引入Javassist工具库

implementation 'org.javassist:javassist:3.28.0-GA'

        首先,先简单生成一个Class文件。运行以下代码,就可以直接生成一个Class文件。

public class GenClass {
    @Test
    public static void main(String[] args) throws Exception {
        //创建一个字节码池,用来存放生成的Class
        ClassPool pool = ClassPool.getDefault();
        // 创建一个User类
        CtClass clz = pool.makeClass("cn.example.User");
        //写入本地
        clz.writeFile();
    }
}

 可以看到,在根目录下直接生成了一个User.Class文件。如下:

先解释一下上面代码的作用:

  • 首先,ClassPool.getDefault(),用来存放我们生成的Class文件,把他加载进内存。
  • 其次,pool.makeClass(),创建一个Class文件,它还有pool.get(),获取一个Class文件。
  • 最后,clz.writeFile(),把Class文件输出出来。可传path,不传就默认输出到根目录。

很明显,如果我们想对Class进行自定义添加操作,那肯定是对clz对象进行操作,可以看看他有哪些方法,如下:

有了方法,那接下来我们看看如何给Class添加构造方法、成员变量、方法和接口。

2.1 添加成员变量

        // 创建一个String变量name
        CtField nameField = new CtField(pool.get("java.lang.String"), "name", clz);
        // 设置name成员变量为私有属性,不设默认Public
        nameField.setModifiers(Modifier.PRIVATE);
        // 将name成员变量添加到Person类中
        clz.addField(nameField);

        // 创建一个Integer变量age
        CtField ageField = new CtField(pool.get("java.lang.Integer"), "age", clz);
        // 设置name成员变量为私有属性,不设默认Public
        ageField.setModifiers(Modifier.PRIVATE);
        // 将name成员变量添加到Person类中
        clz.addField(ageField);

CtField方法参数解释:

/**
 * @param type              变量类型(String、Integer、double等,要写全路径)
 * @param name              变量名
 * @param declaring         声明这个变量添加到哪个Class上
 *
 */
public CtField(CtClass type, String name, CtClass declaring)

效果如下:

 

2.2 添加构造方法

        // 添加一个无参数构造方法
        CtConstructor defaultConstructor = new CtConstructor(new CtClass[]{}, clz);
        // 设置方法体内容
        defaultConstructor.setBody("{name = \"\";}");
        clz.addConstructor(defaultConstructor);

        // 添加一个有参数的构造方法
        CtConstructor paramsConstructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, clz);
        // 设置方法体内容 $0表示this,$1,$2...表示方法参数
        paramsConstructor.setBody("{$0.name = $1;}");
        clz.addConstructor(paramsConstructor);

CtConstructor方法参数解释: 

/**
 * @param parameters        构造的参数类型(String、Integer、double等,要写全路径)
 * @param declaring         声明这个方法添加到哪个Class上
 */
public CtConstructor(CtClass[] parameters, CtClass declaring)

效果如下: 

 2.3 添加方法

        // 添加一个sayHello 无参的方法
        CtMethod sayHello = new CtMethod(CtClass.voidType, "sayHello", new CtClass[]{}, clz);
        //设置方法为PRIVATE,默认为PUBLIC
        sayHello.setModifiers(Modifier.PRIVATE);
        //设置方法体内容
        sayHello.setBody("System.out.println(\"hello, this is \" + $0.name);");
        clz.addMethod(sayHello);

        // 添加一个sayHello 有参的方法
        CtMethod sayHelloWithParam = new CtMethod(CtClass.voidType, "sayHello", new CtClass[]{pool.get("java.lang.String")}, clz);
        //设置方法为PRIVATE,默认为PUBLIC
        sayHelloWithParam.setModifiers(Modifier.PRIVATE);
        //设置方法体内容
        sayHelloWithParam.setBody("System.out.println(\"hello, this is \" + $1);");
        clz.addMethod(sayHelloWithParam);

CtMethod方法参数解释: 

/**
 * @param returnType        返回类型
 * @param mname             方法名
 * @param parameters        方法参数(String、Integer、double等,要写全路径)
 * @param declaring         声明这个方法添加到哪个Class上
 */
public CtMethod(CtClass returnType, String mname,
                CtClass[] parameters, CtClass declaring)

 效果如下:

PS:一个打印的是成员变量name,一个是传进来的变量

三、用Javassist修改Class文件

        除了要如何生成一个Class文件外,我们还需要知道如何对一个已有的Class文件进行修改。

3.1 加载Class文件到内存

        想要对Class进行操作前,那肯定需要将Class加载进内存,不然操作空气嘛😀。代码如下:

        //创建一个字节码池,用来存放加载进来的Class
        ClassPool pool = ClassPool.getDefault();
        //添加Class路径,不能包括包名
        pool.appendClassPath("D:\\20210426\\code\\otherCode\\GradlePluginDemo");
//        pool.insertClassPath("D:\\20210426\\code\\otherCode\\GradlePluginDemo");
        // 获取User.Class,不能加.class
        CtClass clz = pool.get("cn.example.User");
        //非常重要的一步,不是自己创建的Class,都需要先调用defrost,才可以进行修改Class。
        clz.defrost();

PS:appendClassPath和insertClassPath区别

  • 在ClassPool池中有一个搜索列表(链表结构),用来提供给pool.get获取class文件的来源。
  • appendClassPath是添加到搜索列表最后。
  • insertClassPath是添加到搜索列表最前面,如果先append,在insert,会把之前append的数据放在insert之后。

 3.2 删除Class成员变量

删除名字是name的成员变量

        for (CtField field : clz.getDeclaredFields()) {
            System.out.println("field name:"+field.getName());
            if (field.getName().equals("name")){
                clz.removeField(field);
            }
        }

 3.3 删除Class构造方法

删除名字是User的构造方法

        for (CtConstructor constructor : clz.getDeclaredConstructors()) {
            System.out.println("constructor name:"+constructor.getName());
            if (constructor.getName().equals("User")){
                clz.removeConstructor(constructor);
            }
        }

 3.4 删除Class方法

删除名字是sayHello方法

        for (CtMethod method : clz.getDeclaredMethods()) {
            System.out.println("method name:"+method.getName());
            if (method.getName().equals("sayHello")){
                clz.removeMethod(method);
            }
        }

3.5 写回本地

修改完成别忘记写回本地,释放内存。当然,如果进程都结束了就没必要释放了。

        //把修改的内容写入文件
        clz.writeFile(fileName)
        //释放内存
        clz.detach()

四、如何测试?

        看完了如何生成和修改一个Class文件,那怎么能快速的知道有没有生效呢?(如果你有好的方案欢迎评论)答案如下:

1.在Class类中添加一个main方法,main方法中调用sayHello方法(不需要测试方法的也可以不用调用)。

        //添加一个main方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "main", new CtClass[]{pool.get(String[].class.getName())}, clz);
        //将main方法声明为public static类型
        ctMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
        //设置方法体
        ctMethod.setBody("{" +
                "sayHello(\"hello, this is \");" +
                "}");
        clz.addMethod(ctMethod);

注意,main方法是static的,那sayHello也要设置成static,代码如下:

sayHelloWithParam.setModifiers(Modifier.PRIVATE+ Modifier.STATIC);

2. 把生成的Class文件实例化出来,genClass也就是上面生成的User.Class文件

 public static void main(String[] args) throws Exception {
        //测试
        Class clazz = genClass();
        Object obj = clazz.newInstance();
        Method mainMethod = clazz.getMethod("main", new Class[]{String[].class});
        mainMethod.invoke(obj, new String[1]);
    }

3.运行代码

效果如下:

可根据自己想要测试的方法自行修改 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Javassist是一个Java字节码编辑器,可以用于在运行动态地修改类文件。以下是使用Javassist的基本步骤: 1. 添加Javassist库到项目中。你可以从官方网站(http://www.javassist.org/)下载最新版本的Javassist库,并将其添加到项目中。 2. 创建ClassPool实例。ClassPool是Javassist的核心类之一,它用于加载要编辑的类文件。 3. 从ClassPool中获取要编辑的类。你可以使用ClassPool.get()方法来获取一个CtClass对象,该对象表示要编辑的类。 4. 修改类文件。你可以使用CtClass类提供的方法来修改类文件,例如添加字段、修改方法等。 5. 将修改后的类文件保存到磁盘或加载到JVM中。你可以使用CtClass.writeFile()方法将修改后的类文件保存到磁盘,也可以使用ClassLoader将修改后的类文件加载到JVM中。 以下是一个简单的示例代码,演示了如何使用Javassist添加一个新的方法到一个类中: ```java ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.example.MyClass"); CtMethod m = CtNewMethod.make( "public void myNewMethod() { System.out.println(\"Hello from myNewMethod!\"); }", cc); cc.addMethod(m); cc.writeFile(); ``` 在这个例子中,我们首先获取了一个名为"com.example.MyClass"的CtClass对象,然后使用CtNewMethod.make()方法创建了一个名为myNewMethod的新方法,并将其添加到了CtClass对象中。最后,我们使用CtClass.writeFile()方法将修改后的类文件保存到磁盘。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值