javassist学习

 

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

        在 Javassist 中,类 Javaassit.CtClass 表示 class 文件。一个 GtClass (编译时类)对象可以处理一个 class 文件

看代码示例:

加入依赖:

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

使用 Javassist 创建一个 class 文

代码:

package com.sqz.javassist;

import javassist.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CreatePerson {

    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("com.sqz.javassist.Persion");
        //增加成员变量
        CtField name = new CtField(pool.get("java.lang.String"), "name", ctClass);
        name.setModifiers(Modifier.PRIVATE);
        ctClass.addField(name,CtField.Initializer.constant("xiaoming"));
        ctClass.addMethod(CtNewMethod.setter("setName",name));
        ctClass.addMethod(CtNewMethod.getter("getName",name));
        //增加无参构造器
        CtConstructor noArgConstructor = new CtConstructor(new CtClass[]{}, ctClass);
        noArgConstructor.setBody("{name=\"xiaoming\";}");
        ctClass.addConstructor(noArgConstructor);
        //增加有参构造器
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass);
        // $0=this / $1,$2,$3... 代表方法参数
        ctConstructor.setBody("{$0.name=$1;}");
        ctClass.addConstructor(ctConstructor);
        //增加方法
        CtMethod printName = new CtMethod(CtClass.voidType, "printName", new CtClass[0], ctClass);
        printName.setModifiers(Modifier.PUBLIC);
        printName.setBody("{System.out.println(name);}");
        ctClass.addMethod(printName);
        //将生成的class输出到文件
       // ctClass.writeFile("/Users/mac/workspace/dubbo-dubbo-3.0.2/dubbo-demo-sqz/src/main/java/");

        //实例化对象并调用
        Object o = ctClass.toClass().newInstance();
        Method setName = o.getClass().getMethod("setName", String.class);
        Method getName = o.getClass().getMethod("getName");
        Method printName1 = o.getClass().getMethod("printName");
        setName.invoke(o,"老王");
        Object invoke = getName.invoke(o);
        System.out.println("getName->"+invoke);
        printName1.invoke(o);

    }


}

运行上面代码会生成class文件

 class文件内容如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sqz.javassist;

public class Persion {
    private String name = "xiaoming";

    public void setName(String var1) {
        this.name = var1;
    }

    public String getName() {
        return this.name;
    }

    public Persion() {
        this.name = "xiaoming";
    }

    public Persion(String var1) {
        this.name = var1;
    }

    public void printName() {
        System.out.println(this.name);
    }
}

ClassPool需要关注的方法:

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

CtClass需要关注的方法:

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

CtMethod中的一些重要方法:

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

CtField中的一些重要方法 

  1. setModifiers: 在方法的起始位置插入代码;
  2. setGenericSignature: 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
  3. make : 创建一个新的方法。

 修改现有的类对象

代码如下

package com.sqz.javassist;


public class UserService {

    public void printUser(){
        System.out.println("user");
    }

}
z.javassist;

import javassist.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class UpdatePerson {

    public static void main(String[] args) throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get("com.sqz.javassist.UserService");

        //修改方法 类似aop
        CtMethod printUser = ctClass.getDeclaredMethod("printUser");
        printUser.insertBefore("System.out.println(\"before\");");
        printUser.insertAfter("System.out.println(\"after\");");

        //增加方法
        CtMethod show = new CtMethod(CtClass.voidType, "show", new CtClass[0], ctClass);
        show.setBody("{System.out.println(\"show\");}");
        ctClass.addMethod(show);

        //增加字段 这种更简单
        CtField name = CtField.make("private String name = \"老王\";", ctClass);
        ctClass.addField(name);

        //增加方法
        CtMethod getName = CtNewMethod.make("public String getName() {\n" +
            "        return name;\n" +
            "    }", ctClass);
        ctClass.addMethod(getName);


        Object o = ctClass.toClass().newInstance();
        Method printUser1 = o.getClass().getMethod("printUser");
        printUser1.invoke(o);


        Method show1 = o.getClass().getMethod("show");
        show1.invoke(o);


        Method getName1 = o.getClass().getMethod("getName");
        Object invoke = getName1.invoke(o);
        System.out.println(invoke);

    }

}

一般会遇到的使用场景应该是修改已有的类。比如常见的日志切面,权限切面等

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值