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*/@Testpublic void modifyExistedMethod() throws Exception {//类库池, jvm中所加载的classClassPool 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*/@Testpublic void addMethod() throws Exception {//类库池, jvm中所加载的classClassPool 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);}/*** 动态创建类*/@Testpublic 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代表参数1m.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.Studentat 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中所加载的classClassPool pool = ClassPool.getDefault();//获取指定的Student类CtClass ctClass = pool.get("com.xxx.bill.api.Student");.....
}代码修复后
/*** 修改已有方法体,插入新的代码* @throws Exception*/
@Test
public void modifyExistedMethod() throws Exception {//类库池, jvm中所加载的classClassPool 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中所加载的classClassPool 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中所加载的classClassPool 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

Javassist学习文档相关推荐

  1. FreeMarker中文帮助手册API文档,基础入门学习文档

    FreeMarker中文帮助手册API文档,基础入门学习文档 分类: 编程技术 发布: bywei 浏览: 7 日期: 2011年5月28日 分享到: QQ空间 新浪微博 腾讯微博 人人网 什么是Fr ...

  2. ffmpeg的中文学习文档

    ffmpeg的中文学习文档 文章目录: 一.ffmpeg介绍 二.学习参考文档 1.中文 一.ffmpeg介绍 ffmpeg是视频处理工具,可选参数非常多,功能也非常的强大,可以用来开发各种视频处理工 ...

  3. Ext JS 6学习文档-第3章-基础组件

    Ext JS 6学习文档-第3章-基础组件 基础组件 在本章中,你将学习到一些 Ext JS 基础组件的使用.同时我们会结合所学创建一个小项目.这一章我们将学习以下知识点: 熟悉基本的组件 – 按钮, ...

  4. Ext JS 6学习文档-第6章-高级组件

    Ext JS 6学习文档-第6章-高级组件 高级组件 本章涵盖了高级组件,比如 tree 和 data view.它将为读者呈现一个示例项目为 图片浏览器,它使用 tree 和 data view 组 ...

  5. NodeJS-001-Nodejs学习文档整理(转-出自http://www.cnblogs.com/xucheng)

    Nodejs学习文档整理 http://www.cnblogs.com/xucheng/p/3988835.html 1.nodejs是什么: nodejs是一个是javascript能在后台运行的平 ...

  6. java学习文档_阿里技术专家带你玩转JVM,从底层源码到项目实战,都在这份文档里...

    作为 Java 的从业者,在找工作的时候,一定会被问及关于 JVM 相关的知识. JVM 知识的掌握程度,在很多面试官眼里是候选人技术深度的一个重要评判标准.而大多数人可能没有对 JVM 的实际开发和 ...

  7. [扩展阅读] EasyGUI 学习文档【超详细中文版】

    [扩展阅读] EasyGUI 学习文档[超详细中文版] 0. 安装 EasyGUI 官网:https://github.com/robertlugg/easygui python查看内置的所有模块 h ...

  8. C和C++编程和学习文档

     C和C++编程和学习文档 C和C++编程和学习文档   1 :指针变量名称以p为首字符,这是程序员通常在定义指针时的一个习惯 2 :har * p;    (int *)p 把p强制转换为int型  ...

  9. PHP学习文档——基础篇

    PHP学习文档--基础篇 PHP学习文档--基础篇 标记 短标记 脚本标记 标准标记(常用) PHP注释 行注释 块注释 PHP语句分隔符 变量 变量命名规则 预定义变量 可变变量 变量传值 常量 定 ...

  10. Terracotta学习文档

    Terracotta 学习文档    目 录 1.    引言..................................................................... ...

最新文章

  1. Kotlin基本语法和使用
  2. python工程~多个python文件如何运行
  3. 计算机网络-基本概念(9)【传输层】TCP拥塞控制 【网络层】拥塞避免
  4. springboot 打印slf4_SpringBoot 整合 slf4j 日志打印
  5. java map对象排序输出_java实现对象的排序(List和Map)
  6. 智力与体力的人种矛盾
  7. Android开发--内容提供者(Content provider)
  8. Agisoft Photoscan/Mateshape 相机参数XML解读
  9. 欧式香草期权(普通看涨、看跌)的定价公式及实现
  10. Windows PE探秘
  11. 十六条超炫代码让你的QQ空间改头换面(转)
  12. 游戏服务器和普通服务器的区别
  13. hook koa web 码云_Doodoo.js 发布 1.1.0,Koa.js+ Nuxt.js 最佳实践
  14. Docker(狂神说)笔记
  15. pthon缺陷检测(机器视觉)
  16. 用matlab绘制克莱因瓶,克莱因瓶怎么装水动态四维模拟图 克莱因瓶为什么装不满怎么倒水...
  17. 《Google软件工程之道》软件工程随想
  18. 解决u盘插入有反应但是无盘符
  19. 面试中常被问到(二)对齐方式
  20. 「需求广场」需求词更新明细(十三)

热门文章

  1. 上网行为管理网络分析系统
  2. Unity从Asset Store下载的资源安装包默认的保存位置及其修改
  3. android游戏妄撮java源码
  4. 弹窗插件zDialog使用教程
  5. 火车预购票系统设计JAVA_火车订票系统的设计与实现
  6. 以太网转串口方案总结
  7. 融合黄金正弦混合变异的自适应樽海鞘群算法
  8. 最新第一波:全国信息化工程师软考-系统集成项目管理工程师(高级案例高分论文)
  9. PHP云和骑士人才优缺点,PHP云人才系统与骑士cms人才系统对比点评
  10. Adobe官方清理工具Adobe Creative Cloud Cleaner Tool使用教程