> 文章列表 > Javassist动态生成类使用,以及Arthas 查看动态代理生成类内容

Javassist动态生成类使用,以及Arthas 查看动态代理生成类内容

Javassist动态生成类使用,以及Arthas 查看动态代理生成类内容

java反射

反射的弊端:

性能开销:
因为反射涉及到动态解析的类型,所以某些Java虚拟机的优化不能被执行(因为它不能真正了解你在做什么)。因此,反射操作的性能比非反射操作的性能要慢,应该避免在对性能敏感的应用程序中频繁调用的代码部分。

Javassist

  1. 操作Java字节码简单,是一个用于编辑Java字节码的类库,可以在运行时定义一个新类,并在JVM加载类文件是修改它
  2. Javassist提供了两种级别的API:源级别和字节码级别。如果用户使用源代码级API,可以不需要了解Java字节码的规范的前提下编辑类文件。整个API仅使用Java语言的词汇表设计。甚至你可以以源文本的形式插入字节码中;Javassist动态编译它。另一方面,字节码级API允许用户作为编辑器直接编辑类文件。
  3. Javassist允许检查、编辑和创建Java二进制类。
  4. Javassist并不是唯一处理字节码的库,但它有一个特别功能,使其成为一个重要的开始来尝试字节码工作:你可以使用Javassist改变一个Java类的字节码而不需要学习任何关于字节码或Java虚拟机(JVM)的体系结构。
  5. 面向切面编程:Javassist可以向类中添加新方法,以及在调用方和被调用方两边插入before/after通知。
  6. 反射:Javassist另一个应用就是运行时反射;Javassist允许Java程序使用一个元对象,该元对象控制基级别对象上的方法调用。不需要专门的编译器或虚拟机。
  7. Javassist还提供了用于直接编辑类文件的低级API。要使用此级别的API,需要了解Java字节码和类文件格式,而此级别的API允许您对类文件进行任何类型的修改。

Maven 依赖

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

常用类常用方法

ClassPool常用方法:
  • getDefault : 返回默认的ClassPool 是单例模式的,通过该方法创建ClassPool对象;
  • appendClassPath, insertClassPath : 将一个ClassPath加到类搜索路径的末尾位置 或 插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬;
  • toClass : 将修改后的CtClass加载至当前线程的上下文类加载器中,并转换成Class对象。一旦调用该方法,则无法继续修改已经被加载的class;
  • get , getCtClass : 根据类路径名获取该类的CtClass对象(如果类路径名不存在,抛异常),用于后续的编辑。get()并不搜索所记录的包。只有编译器会搜索它。
  • makeClass:创建一个新的公共类。如果已经存在同名的类/接口,则新类将覆盖前一个类。 如果没有显式地向创建的新类添加构造函数,Javassist将生成构造函数,并在生成类文件时添加它。它为超类的每个构造函数生成一个新的构造函数。新的构造函数接受相同的参数集,并调用超类的相应构造函数。所有接收到的参数都传递给它。
  • importPackage:记录包名,以便Javassist编译器搜索包以解析类名。不要记录java.Lang包,默认情况下已隐式记录。 从3.14版开始,packageName可以是一个完全限定的类名。
CtClass常用方法:
  • freeze : 冻结一个类;
  • isFrozen : 判断一个类是否已被冻结;
  • prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;
  • defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法;
  • detach : 将该class从ClassPool中删除;
  • writeFile : 根据CtClass生成 .class 文件;
  • toClass : 通过类加载器加载CtClass。转换成Class对象
  • addMethod:在目标类中添加一个方法
  • removeMethod : 对目标类删除一个方法

上面我们创建一个新的方法使用了CtMethod类。CtMthod代表类中的某个方法,可以通过CtClass提供的API获取或者CtNewMethod新建,通过CtMethod对象可以实现对方法的修改。

CtMethod常用方法:

实现自CtBehavior的方法

  • insertBefore : 在方法的起始位置插入代码;
  • insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,当抛出异常时,它不会执行;
  • insertAt : 在指定的位置插入代码;
  • setBody : 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除;

自己的方法

  • make : 创建一个新的方法。

CtField常用方法:

使用Javassist注意事项

javassist中写入函数体时:

  1. 对于范型符号需要特殊处理 例如 /*<?>*/
  2. 对饮用的外部类显式声明包路径 例如 com.example.javassist.IJavassistService

例如下面构造函数体内容

public static String buildMethod(String impl) throws Exception {String methodString = "   public String hello(String name) {\\n"+ "        System.out.println(\\"before iJavassistService method ..\\");\\n"+ "        String className = \\"com.example.javassist." + impl + "\\";\\n"+ "        Class/*<?>*/ serviceClassName = Class.forName(className);\\n "+ "        com.example.javassist.IJavassistService iJavassistService = (com.example.javassist.IJavassistService) serviceClassName.newInstance();\\n"+ "        String nameMethod = iJavassistService.hello(name);\\n"+ "        System.out.println(\\"after iJavassistService method ..\\");\\n"+ "        return nameMethod;\\n"+ "    }";return methodString;
}

手写Dubbo中spi扩展机制的 protocolSPI.export(Invoker invoker)

简单根据代码模仿dubbo SPI机制中 使用javassist 的思想

IJavassistService 接口

public interface IJavassistService {String hello(String name);
}

IJavaServiceImplPlus 和 IJavassistServiceImpl 实现类

public class IJavassistServiceImpl implements IJavassistService{@Overridepublic String hello(String name) {System.out.println( "javassist service impl " + name);return "javassist service impl " + name;}
}
// - - - - - - - - - - - - - - - - - - - - -
public class IJavaServiceImplPlus implements IJavassistService{@Overridepublic String hello(String name) {System.out.println( "service plus    " + name);return "service plus" + name;}
}

JavassistTest 测试类

public static void main(String[] args) throws Exception {classPoolTest("IJavaServiceImplPlus","lucy");
}public static void classPoolTest(String implName,String hello) throws Exception {ClassPool cp = ClassPool.getDefault();// makeClass 构造一个类CtClass ctClass = cp.makeClass("com.example.javassist.IJavassistServiceImplProxy");
//        CtField age = new CtField(cp.get("java.lang.Integer"), "age", ctClass);
//        age.setModifiers(Modifier.PUBLIC);// 添加实现接口ctClass.setInterfaces(new CtClass[]{cp.getCtClass("com.example.javassist.IJavassistService")});// 添加自定义方法CtMethod make = CtNewMethod.make(buildMethod(implName), ctClass);ctClass.addMethod(make);// 实例化类IJavassistService iJavassistService = (IJavassistService) ctClass.toClass().newInstance();String hello1 = iJavassistService.hello(hello);// 输出返回结果System.out.println(hello1);
}public static String buildMethod(String impl) throws Exception {String methodString = "   public String hello(String name) {\\n"+ "        System.out.println(\\"before iJavassistService method ..\\");\\n"+ "        String className = \\"com.example.javassist." + impl + "\\";\\n"+ "        Class/*<?>*/ serviceClassName = Class.forName(className);\\n "+ "        com.example.javassist.IJavassistService iJavassistService = (com.example.javassist.IJavassistService) serviceClassName.newInstance();\\n"+ "        String nameMethod = iJavassistService.hello(name);\\n"+ "        System.out.println(\\"after iJavassistService method ..\\");\\n"+ "        return nameMethod;\\n"+ "    }";return methodString;
}

javassist为什么慢

首先javassist 内部使用的反射,本身反射就慢,不能使用jvm内部的一些优化

查看代理类内容

使用Arthas查看

arthas官网:

https://arthas.aliyun.com/doc

Arthas(阿尔萨斯)能为你做什么?

Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到 JVM 的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?
  8. 怎样直接从 JVM 内查找某个类的实例?

Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

怎么查看代理类?

下载后 cmd进入arthas-boot.jar 目录进入命令行 执行jar文件

Javassist动态生成类使用,以及Arthas 查看动态代理生成类内容

sc *目标代理类名称*命令 查找目标代理类位置

以Dubbo中的Protocol@Adaptive 为例

sc *Protocol$Adaptive* 搜索 类名字 所在包位置

jad org.apache.dubbo.rpc.Protocol$Adaptive查看 目标类字节码文件

Javassist动态生成类使用,以及Arthas 查看动态代理生成类内容

Javassist动态生成类使用,以及Arthas 查看动态代理生成类内容