javassist初步学习

javassist

Javassist是一个开源的分析、编辑和创建Java字节码的类库。性能比BCEL、ASM等要差,但是使用不需要了解虚拟机指令。使用简单且快速。

操作字节码的可实现的功能

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

与反射相较的优势

比反射开销小,性能高。

javassist的使用

1.导入javassist的jar包

可以通过官网找到 http://www.javassist.org/

也可以通过maven直接依赖

        <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.26.0-GA</version>
        </dependency>

2.通过javassist来创建一个class

        //获取Class池
        ClassPool pool = ClassPool.getDefault();
        //生成一个新的CtClass
        CtClass cclz = pool.makeClass("com.rainc.bean.Emp");
        //通过make创建属性    直接输入源码即可
        CtField f1 = CtField.make("private int empno;", cclz);
        //将属性添加至CtClass
        cclz.addField(f1);
        //通过构造器创建属性    基本类型可以通过CtClass.来获得如CtClass.intType,若是其它类型则要通过pool.get
        CtField f2 = new CtField(pool.get("java.lang.String"), "ename", cclz);
        //设置修饰符
        f2.setModifiers(Modifier.PRIVATE);
        cclz.addField(f2);

        //通过make创建方法
        CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cclz);
        //通过构造器创建方法
        CtMethod m2 = new CtMethod(CtClass.voidType, "setEmpno", new CtClass[]{CtClass.intType}, cclz);
        m2.setModifiers(Modifier.PUBLIC);
        //$0相当于this,$1开始表示的是第几个形参$1是第一个$2是第二个。
        m2.setBody("{$0.empno=$1;}");
        cclz.addMethod(m1);
        cclz.addMethod(m2);
        //如果是某个属性的get和set方法还可以通过CtNewMethod类来创建
        CtMethod m3 = CtNewMethod.getter("getEname", f2);
        CtMethod m4 = CtNewMethod.setter("setEname", f2);
        cclz.addMethod(m3);
        cclz.addMethod(m4);

        //添加构造器
        CtConstructor constructor1 = new CtConstructor(new CtClass[]{CtClass.intType, pool.get("java.lang.String")}, cclz);
        constructor1.setBody("{$0.empno=$1;$0.ename=$2;}");
        CtConstructor constructor2 = CtNewConstructor.make("public Emp(){}", cclz);
        cclz.addConstructor(constructor1);
        cclz.addConstructor(constructor2);
        cclz.writeFile("E:/myjava");//将上面构造好的类写入到E:/myjava里
        System.out.println("生成类,成功");

通过反编译软件查看所生成的class文件

如果使用的是idea的话直接将class文件拖至idea即可,因为idea内置有反编译。

package com.rainc.bean;

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

    public int getEmpno() {
        return this.empno;
    }

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

    public String getEname() {
        return this.ename;
    }

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

    public Emp(int var1, String var2) {
        this.empno = var1;
        this.ename = var2;
    }

    public Emp() {
    }
}

3.通过反射来调用javassist生成的class类

        //接之前创建class的代码
        //获得Class类
        Class clz = cclz.toClass();
        Object obj = clz.getConstructor().newInstance();
        //取得setEname方法
        Method method1 = clz.getMethod("setEname", String.class);
        //调用setEname方法
        method1.invoke(obj, "rainc");
        //取得getEname方法
        Method method2 = clz.getMethod("getEname");
        //调用getEname方法并取得返回值
        Object result = method2.invoke(obj);
        System.out.println(result);

可以看到成功的设置了属性值并取得了,红色的是高版本java反射的警告忽视即可。

4.通过javassist操作已存在的类

创建测试用的类

package com.rainc.test;

/**
 * @Author rainc
 * @create 2019/10/25 19:39
 */
public class Emp {
    private String ename;
    private int empno;

    public String getEname() {
        return ename;
    }

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

    public int getEmpno() {
        return empno;
    }

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

    public Emp(String ename, int empno) {
        this.ename = ename;
        this.empno = empno;
    }

    public Emp() {
    }

    public void hello(){
        System.out.println("helloWorld");
    }
}

得到类的信息

       ClassPool pool = ClassPool.getDefault();
        //通过类全名得到该类
        CtClass ctclz = pool.get("com.rainc.test.Emp");
        System.out.println(ctclz.getName());//获得类全名
        System.out.println(ctclz.getSimpleName());//获得类名
        System.out.println(ctclz.getSuperclass());//获得父类
        System.out.println(ctclz.getInterfaces());//获得接口

生成新的方法

        ClassPool pool = ClassPool.getDefault();
        //通过类全名得到该类
        CtClass ctclz = pool.get("com.rainc.test.Emp");
        //添加了一个求和的方法
         CtMethod m = CtMethod.make("public int sum(int a,int b){return a+b;}", ctclz);
        ctclz.addMethod(m);

        //通过反射调用新生成的方法
        Class clz = ctclz.toClass();
        Object obj = clz.getConstructor().newInstance();
        Method method = clz.getMethod("sum", int.class, int.class);
        Object result = method.invoke(obj, 200, 300);
        System.out.println(result);

修改已有的方法

        ClassPool pool = ClassPool.getDefault();
        //通过类全名得到该类
        CtClass ctclz = pool.get("com.rainc.test.Emp");

        //取得指定的方法
        CtMethod m = ctclz.getDeclaredMethod("hello");
        //在前面插入
        m.insertBefore("System.out.println(\"start!!!\");");
        //在尾部插入
        m.insertAfter("System.out.println(\"-----end----\");");
        //在class的第几行前插入,这个插入的行数是指java源文件的行数我这里输出helloworld在37行
        m.insertAt(36, "System.out.println(\"-----insert36----\");");
        m.insertAt(37, "System.out.println(\"-----insert37----\");");

        //通过反射调用新生成的方法
        Class clz = ctclz.toClass();
        Object obj = clz.getConstructor().newInstance();
        Method method = clz.getMethod("hello");
        method.invoke(obj);

其它的都大同小异,但是通过提供的api无法操作泛型。注解只能读取无法直接进行修改,需要通过字节码来实现。

学习参考: https://www.bilibili.com/video/av30023103/?p=278
个人博客: https://www.rainc.top/2019/10/26/java-study/Javassist-study/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容