欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java字节码操作之Javassist

程序员文章站 2022-06-18 11:18:12
...

Java的动态性两种方式实现:字节码操作和反射
运行时操作字节码可以让我们实现以下功能:

  • 动态生成新的类
  • 动态改变某个类的结构(添加/删除/修改 新的属性/方法)

操作字节码的优势:

  • 比反射开销小,性能高
  • Javassist性能高于反射,低于ASM

而常见字节码操作类库:
Java字节码操作之Javassist
此次,主要介绍Javassist相关内容:
Javassist是一个开源的分析、编辑和创建Java字节码的类库,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
其中经常使用的方法:

ClassPool:javassist的类池,使用ClassPool 类可以跟踪和控制所操作的类,它的工作方式与 JVM 类装载器非常相似
CtClass: CtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法。
CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等
CtMethod:类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码
CtConstructor:与CtMethod类似

方法体内容代码 $1代表第一个参数,$2代表第二个参数
Java字节码操作之Javassist

下面进行具体操作,使用Javassist创建类Emp:

import javassist.*;

public class Demo2 {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.shsxt.bean.Emp");

        //创建属性
        CtField f1 = CtField.make("private int empno;",cc);
        CtField f2 = CtField.make("private String ename;",cc);
        cc.addField(f1);
        cc.addField(f2);

        //创建方法
        CtMethod m1=CtMethod.make("public int getEmpno(){return empno;}",cc);
        CtMethod m2=CtMethod.make("public void setEmpno(int empno){this.empno=empno;}",cc);
        cc.addMethod(m1);
        cc.addMethod(m2);

        //添加构造器
        CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")},cc);
        constructor.setBody("{this.empno = empno;this.ename=ename;}");
        cc.addConstructor(constructor);

        cc.writeFile("E");//将上面构造好的类写入E盘
        System.out.println("生成类成功");
    }
}

下面使用Javassist对某个类进行操作,首先创建一个将要操作的类Emp:


public class Emp {
    private int empno;
    private String ename;

    public int getEmpno() {
        return empno;
    }

    public void setEmpno(int empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Emp(int empno, String ename) {
        this.empno = empno;
        this.ename = ename;
    }
    public Emp(){}
    public void sayHello(int a){
        System.out.println("say hello,"+a);
    }
}

然后,对Emp类进行相关操作:


import javassist.*;

import java.lang.reflect.Method;
import java.util.Arrays;

/*
* 测试javassist的API
* */
public class Demo03 {
    /*处理类的基本用法*/
    public static void test01() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.shsxt.server.Emp");

        byte[] bytes = cc.toBytecode();
        System.out.println(Arrays.toString(bytes));
        System.out.println(cc.getName());//获取类名
        System.out.println(cc.getSimpleName());//获取简要类名
        System.out.println(cc.getSuperclass());//获得父类
        System.out.println(cc.getInterfaces());//获得接口
    }
    /*测试产生新方法*/
    public static  void test02() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.shsxt.server.Emp");

        // 方法一
        //CtMethod m = CtNewMethod.make("public int add(int a,int b){return a+b;}",cc);

        // 方法二
        //CtClass.intType:返回值类型   "add":方法名称
        // new CtClass[]{CtClass.intType,CtClass.intType}:传递的参数类型
        CtMethod m = new CtMethod(CtClass.intType,"add",new CtClass[]{CtClass.intType,CtClass.intType},cc);
        //声明方法类型为公开的
        m.setModifiers(Modifier.PUBLIC);
        //设置方法中的方法体
        //m.setBody("{System.out.println(\"hahaha\");return a+b;}");
        //由于上面定义并未制定a、b,所以使用return a+b; 会报错
        //解决方法:使用$0表示this  $1表示第一个参数  $2表示第二个参数
        m.setBody("{System.out.println(\"hahaha\");return $1+$2;}");
        cc.addMethod(m);

        //通过反射调用新生成的方法
        Class clazz = cc.toClass();

        //jdk9.0之后newInstance方法已过时
        //Object obj = clazz.newInstance();
        //新的创建反射对象方法
        Object obj = clazz.getConstructor().newInstance();//通过调用Emp无参构造器,创建新的Emp对象
        //获得反射对象方法
        Method method = clazz.getDeclaredMethod("add",int.class,int.class);
        //对方法进行赋值
        Object result=method.invoke(obj,200,300);
        System.out.println(result);
    }

    //修改已有的方法的信息,修改方法的内容
    public static void test03() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.shsxt.server.Emp");

        CtMethod cm = cc.getDeclaredMethod("sayHello",new CtClass[]{CtClass.intType});
        //在sayHello方法的前面加内容
        cm.insertBefore("System.out.println($1);System.out.println(\"start...\");");

        //在sayHello方法的后面加内容
        cm.insertAfter("System.out.println(\"end...\");");
        //通过反射调用新生成的方法
        Class clazz = cc.toClass();
        Object obj = clazz.getConstructor().newInstance();//通过调用Emp无参构造器,创建新的Emp对象
        //获得反射对象方法
        Method method = clazz.getDeclaredMethod("sayHello",int.class);
        //对方法进行赋值
        Object result=method.invoke(obj,200);
        System.out.println(result);
    }

    //属性操作
    public static void test04() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.shsxt.server.Emp");

        //CtField  f1 = CtField.make("private int empno;",cc);
        CtField f1 = new CtField(CtClass.intType,"salary",cc);
        f1.setModifiers(Modifier.PUBLIC);
        cc.addField(f1);

        cc.getDeclaredField("ename");//获取指定的属性

        //增加相应的set和get方法
        cc.addMethod(CtNewMethod.getter("getSalary",f1));
        cc.addMethod(CtNewMethod.setter("setSalary",f1));
    }
    public static void main(String[] args) throws Exception {
        //test01();
        //test02();
        test03();
    }
}
相关标签: Java Javassist