Javassist学习文档

Javassist学习文档

javassist简介

javassist是一个字节码类库,可以用他来动态生成类,动态修改类等等
要想将编译时不存在的类在运行时动态创建并加载,通常有两种策略:

  1. 动态编译
  2. 动态生成二进制字节码(.class)
    对于第二种策略,实际上已经有诸多比较成熟的开源项目提供支持,如CGLib、ASM、Javassist等。这些开源项目通常都具备两方面的功能:
  3. 动态创建新类或新接口的二进制字节码
  4. 动态扩展现有类或接口的二进制字节码
    其中,CGLib的底层基于ASM实现,是一个高效高性能的生成库;而ASM是一个轻量级的类库,但需要涉及到JVM的操作和指令;相比而言,Javassist要简单的多,完全是基于Java的API,但其性能相比前二者要差一些。
    尽管如此,在性能要求相对低的场合,Javassist仍然十分有用,如JBoss中就调用了Javassist。

javassist用法

增加javassist依赖:

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

下面示例中使用的javassist相关类都是取自额外添加的依赖包中(不使用mybatis组件中的javassist包)

代码示例
Student类

public class Student {

    private int age;
    private String name;

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void sayHello() {
        System.out.println("Hello, My name is "+this.name);
    }
}

JavassistDemo类

import javassist.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.lang.reflect.Method;

@Slf4j
@SuppressWarnings("unchecked")
public class JavassistDemo {

    /**
     * 修改已有方法体,插入新的代码
     * @throws Exception
     */
    @Test
    public void modifyExistedMethod() throws Exception {

        //类库池, jvm中所加载的class
        ClassPool pool = ClassPool.getDefault();
//        pool.insertClassPath(new ClassClassPath(Student.class));
        ClassClassPath classPath = new ClassClassPath(this.getClass());
        pool.insertClassPath(classPath);
        //获取指定的Student类
        CtClass ctClass = pool.get("com.xxx.bill.api.Student");
        //获取sayHello方法
        CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
        //在方法的代码后追加 一段代码
        ctMethod.insertAfter("System.out.println(\"I'm \"+this.age+\"yeas old.\");");
//        ctClass.writeFile();
        //使用当前的ClassLoader加载被修改后的类
        Class<Student> newClass = ctClass.toClass();
//        Student1 stu = newClass.newInstance();
        Student stu = newClass.getDeclaredConstructor().newInstance();
        stu.setName("张三");
        stu.setAge(18);
        stu.sayHello();
    }

    /**
     * 动态添加方法
     * @throws Exception
     */
    @Test
    public void addMethod() throws Exception {
        //类库池, jvm中所加载的class
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(this.getClass());
        pool.insertClassPath(classPath);
        // 获取指定的Student类
        CtClass ctClass = pool.get("com.xxx.bill.api.Student");
        // 创建calc方法, 带两个参数,参数的类型都为int类型
        CtMethod ctMethod = new CtMethod(CtClass.intType, "calc",
                                                        new CtClass[]{CtClass.intType,CtClass.intType},
                                                        ctClass);
        //设置方法的访问修饰
        ctMethod.setModifiers(Modifier.PUBLIC);
        //设置方法体代码
        ctMethod.setBody("return $1 + $2;");
        //添加新建的方法到原有的类中
        ctClass.addMethod(ctMethod);
        //加载修改后的类
        ctClass.toClass();
        //创建对象
        Student stu = new Student();
        //获取calc方法
        Method dMethod = Student.class.getDeclaredMethod("calc",new Class[]{int.class,int.class});
        //反射调用方法
        Object result = dMethod.invoke(stu,10,20);
        //打印结果
        System.out.println(String.format("调用calc方法, 传入参数: %d,%d", 10, 20));
        System.out.println("返回结果: "+(int)result);
    }

    /**
     * 动态创建类
     */
    @Test
    public void createClass() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(this.getClass());
        pool.insertClassPath(classPath);
        //创建teacher类
        CtClass teacherClass = pool.makeClass("com.xxx.bill.api.Teacher");
//        引用包
//        pool.importPackage("java.awt");
        //设置为公有类
        teacherClass.setModifiers(Modifier.PUBLIC);
        //获取String类型
        CtClass stringClass = pool.get("java.lang.String");
        //获取list类型
        CtClass listClass = pool.get("java.util.List");
        //获取学生的类型
        CtClass studentClass = pool.get("com.xxx.bill.api.Student");
        //给teacher添加name属性
//        方法一:
//        CtField nameField = new CtField(stringClass, "name", teacherClass);
//        nameField.setModifiers(Modifier.PUBLIC);
//        teacherClass.addField(nameField);
//        方法二:
        CtField nameField = CtField.make("public String name;", teacherClass);
        teacherClass.addField(nameField);
        //给teacher类添加students属性
        CtField studentList = new CtField(listClass, "students", teacherClass);
        studentList.setModifiers(Modifier.PUBLIC);
        teacherClass.addField(studentList);
        //给teacher类添加无参构造方法
        CtConstructor ctConstructor = CtNewConstructor.make("public Teacher() {\n" +
                "            this.name = \"abc\";\n" +
                "            this.students = new java.util.ArrayList();\n" +
                "        }", teacherClass);
        teacherClass.addConstructor(ctConstructor);
        //给teacher类添加addStudent方法
        CtMethod m = new CtMethod(CtClass.voidType, "addStudent", new CtClass[]{studentClass}, teacherClass);
        m.setModifiers(Modifier.PUBLIC);
//        移除方法:
//        teacherClass.removeMethod(m);
//        移除属性:
//        teacherClass.removeField(nameField);
        //添加学生对象到students属性中,$1代表参数1
        m.setBody("this.students.add($1);");
        teacherClass.addMethod(m);
        //给teacher类添加sayHello方法
        m = new CtMethod(CtClass.voidType, "sayHello", new CtClass[]{}, teacherClass);
        m.setBody("System.out.println(\"Hello, My name is \"+this.name);");
        m.insertAfter("System.out.println(\"I have \"+this.students.size()+\" students\");");
        teacherClass.addMethod(m);
        //加载修改后的类
        Class<?> cls = teacherClass.toClass();
        //实例teacher对象
        Object obj = cls.getDeclaredConstructor().newInstance();
        //获取addStudent方法
        Method addStudentMethod = cls.getDeclaredMethod("addStudent", Student.class);
        //反射调用方法
        addStudentMethod.invoke(obj, new Student());
        addStudentMethod.invoke(obj, new Student());
        //获取sayHello方法
        Method sayHelloMethod = cls.getDeclaredMethod("sayHello");
        sayHelloMethod.invoke(obj);

    }

}

常见问题
NotFoundException
异常信息

javassist.NotFoundException: com.xxx.bill.api.Student

        at javassist.ClassPool.get(ClassPool.java:452)
        at com.xxx.bill.api.JavassistDemo.modifyExistedMethod(JavassistDemo.java:26)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)

代码修复前

/**
 * 修改已有方法体,插入新的代码
 * @throws Exception
 */
@Test
public void modifyExistedMethod() throws Exception {
    //类库池, jvm中所加载的class
    ClassPool pool = ClassPool.getDefault();
    //获取指定的Student类
    CtClass ctClass = pool.get("com.xxx.bill.api.Student");
    .....
}

代码修复后
/**
 * 修改已有方法体,插入新的代码
 * @throws Exception
 */
@Test
public void modifyExistedMethod() throws Exception {
    //类库池, jvm中所加载的class
    ClassPool pool = ClassPool.getDefault();
//  解决方式一:    
//  pool.insertClassPath(new ClassClassPath(JavassistDemo.class));
//  解决方式二:
    ClassClassPath classPath = new ClassClassPath(this.getClass());
    pool.insertClassPath(classPath);
    //获取指定的Student类
    CtClass ctClass = pool.get("com.xxx.bill.api.Student");
    .....
}

问题产生原因
javassist官网对此有如下说明
The default ClassPool returned by a static method ClassPool.getDefault()
searches the same path that the underlying JVM (Java virtual machine) has.
If a program is running on a web application server such as JBoss and Tomcat, the ClassPoolobject may not be able to find user classes since such a web application server uses multiple class loaders as well as the
system class loader.
In that case, an additional class path must be registered to the ClassPool.

翻译
静态方法返回的默认类池ClassPool.getDefault类池()

搜索底层JVM(Java虚拟机)具有的相同路径。

如果程序在JBoss和Tomcat等web应用服务器上运行,那么ClassPoolobject可能无法找到用户类,因为这样的web应用服务器使用多个类加载器以及

系统类加载器。

在这种情况下,必须向类池注册一个额外的类路径。

CannotCompileException
异常信息

javassist.CannotCompileException: by java.lang.LinkageError: loader 'app' (instance of jdk.internal.loader.ClassLoaders$AppClassLoader) attempted duplicate class definition for com.xxx.bill.api.Student.

        at javassist.ClassPool.toClass(ClassPool.java:1170)
        at javassist.ClassPool.toClass(ClassPool.java:1113)
        at javassist.ClassPool.toClass(ClassPool.java:1071)
        at javassist.CtClass.toClass(CtClass.java:1275)
        at com.xxx.bill.api.JavassistDemo.modifyExistedMethod(JavassistDemo.java:33)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: java.lang.LinkageError: loader 'app' (instance of jdk.internal.loader.ClassLoaders$AppClassLoader) attempted duplicate class definition for com.xxx.bill.api.Student.
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:877)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at javassist.ClassPool.toClass2(ClassPool.java:1183)
        at javassist.ClassPool.toClass(ClassPool.java:1164)
        ... 29 more

代码修复前

/**
 * 修改已有方法体,插入新的代码
 * @throws Exception
 */
@Test
public void modifyExistedMethod() throws Exception {
    //类库池, jvm中所加载的class
    ClassPool pool = ClassPool.getDefault();
    //错误方式一
    //Student stu = new Student();
    //错误方式二
    pool.insertClassPath(new ClassClassPath(Student.class));
    //获取指定的Student类
    CtClass ctClass = pool.get("com.xxx.bill.api.Student");
    .....
}

代码修复后
/**
 * 修改已有方法体,插入新的代码
 * @throws Exception
 */
@Test
public void modifyExistedMethod() throws Exception {
    //类库池, jvm中所加载的class
    ClassPool pool = ClassPool.getDefault();
//  解决方式一:    
//  pool.insertClassPath(new ClassClassPath(JavassistDemo.class));
//  解决方式二:
    ClassClassPath classPath = new ClassClassPath(this.getClass());
    pool.insertClassPath(classPath);
    //获取指定的Student类
    CtClass ctClass = pool.get("com.xxx.bill.api.Student");
    .....
}

问题产生原因
一个classloader里面不能有两个重复的对象,除非是两个不同的classloader。
javassist限制

  1. 不支持java5.0的新增语法。不支持注解修改,但可以通过底层的javassist类来解决,具体参考:javassist.bytecode.annotation
  2. 不支持数组的初始化,如String[]{“1”,“2”},除非只有数组的容量为1
  3. 不支持内部类和匿名类
  4. 不支持continue和break 表达式。
  5. 对于继承关系,有些不支持。例如
class A {} 
class B extends A {} 
class C extends B {} 

class X { 
  void foo(A a) { .. } 
  void foo(B b) { .. } 
}

如果调用 x.foo(new C()),可能会调用foo(A) 。

  1. 推荐开发者用#分隔一个class name和static method或者 static field。例如:
    javassist.CtClass.intType.getName()推荐用javassist.CtClass#intType.getName()

参考网址
http://www.javassist.org/
https://zhuanlan.zhihu.com/p/141449080
https://www.cnblogs.com/sunfie/p/5154246.html
https://blog.csdn.net/weixin_33885253/article/details/91947082

Todolist
1.生成javafile
2.卸载类
3.类加载器
serverless

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值