java反射
反射的弊端:
性能开销:
因为反射涉及到动态解析的类型,所以某些Java虚拟机的优化不能被执行(因为它不能真正了解你在做什么)。因此,反射操作的性能比非反射操作的性能要慢,应该避免在对性能敏感的应用程序中频繁调用的代码部分。
Javassist
- 操作Java字节码简单,是一个用于编辑Java字节码的类库,可以在运行时定义一个新类,并在JVM加载类文件是修改它
- Javassist提供了两种级别的API:源级别和字节码级别。如果用户使用源代码级API,可以不需要了解Java字节码的规范的前提下编辑类文件。整个API仅使用Java语言的词汇表设计。甚至你可以以源文本的形式插入字节码中;Javassist动态编译它。另一方面,字节码级API允许用户作为编辑器直接编辑类文件。
- Javassist允许检查、编辑和创建Java二进制类。
- Javassist并不是唯一处理字节码的库,但它有一个特别功能,使其成为一个重要的开始来尝试字节码工作:你可以使用Javassist改变一个Java类的字节码而不需要学习任何关于字节码或Java虚拟机(JVM)的体系结构。
- 面向切面编程:Javassist可以向类中添加新方法,以及在调用方和被调用方两边插入before/after通知。
- 反射:Javassist另一个应用就是运行时反射;Javassist允许Java程序使用一个元对象,该元对象控制基级别对象上的方法调用。不需要专门的编译器或虚拟机。
- 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中写入函数体时:
- 对于范型符号需要特殊处理 例如
/*<?>*/
- 对饮用的外部类显式声明包路径 例如
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{
@Override
public String hello(String name) {
System.out.println( "javassist service impl " + name);
return "javassist service impl " + name;
}
}
// - - - - - - - - - - - - - - - - - - - - -
public class IJavaServiceImplPlus implements IJavassistService{
@Override
public 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可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
怎么查看代理类?
下载后 cmd进入arthas-boot.jar 目录进入命令行 执行jar文件
sc *目标代理类名称*
命令 查找目标代理类位置
以Dubbo中的Protocol@Adaptive 为例
sc *Protocol$Adaptive*
搜索 类名字 所在包位置
jad org.apache.dubbo.rpc.Protocol$Adaptive
查看 目标类字节码文件