首页 > 编程知识 正文

java调用链跟踪,java 链路跟踪框架

时间:2023-05-04 15:16:30 阅读:224772 作者:2120

collie

使用Java实现一个分布式调用链追踪系统

现在项目已经开源,欢迎提pr和star,项目地址:分布式调用链追踪系统Collie

项目系列博客地址:
柠檬好酸啊:用Java实现一个分布式调用链追踪系统 (一)聊聊自己的想法
柠檬好酸啊:用java实现一个分布式调用链追踪系统(二)项目搭建过程中的一些注意事项
柠檬好酸啊:用java实现一个分布式调用链追踪系统(三)最核心的实现之Javassist
柠檬好酸啊:用java实现一个分布式调用链追踪系统(四)项目具体实现
柠檬好酸啊:用java实现一个分布式调用链追踪系统(五)总结

初识javassist

Javassist是一个开源的分析、编辑和创建Java字节码的类库。使用Javassist可以对java字节码进行修改,和Java的反射类似,但是比反射更强大。对比同样的Java字节码修改工具ASM,Javassist更加方便简介,但是性能上稍微差了一点。

写一个helloworld

我们的目标是用javassit生成一个类,可以输出“hello world”,我们的目标生成类是:

package com.github.blocks.bytecode.javassit;public class HelloWorld { public static void main(String[] var0) { System.out.println("hello world"); } public HelloWorld() { }}

用javassit生成的代码如下:

public class JavassitHelloWorld { public static void main(String[] args) throws Exception{ // 最核心的对象,可以创建对象 ClassPool cp = ClassPool.getDefault(); // 创建类,类的全类名 CtClass ctClass = cp.makeClass("com.github.blocks.bytecode.javassit.HelloWorld");// 创建一个方法,返回值类型,方法名,参数类型,属于哪个类 CtMethod main = new CtMethod(CtClass.voidType, "main", new CtClass[] {cp.get(String[].class.getName())}, ctClass); main.setModifiers(Modifier.PUBLIC+Modifier.STATIC); // 函数体 main.setBody("{System.out.println("hello world");}"); // 把方法添加到类里 ctClass.addMethod(main); // 创建无参数构造方法 CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass); ctConstructor.setBody("{}"); ctClass.addConstructor(ctConstructor); // 输出类内容,可以加上路径,会输出class文件 ctClass.writeFile("/Users/dongzhonghua03/Documents/github/spring-cloud-blocks/byte-code-exp/src/main/java"); // 测试调用 Class<?> clazz = ctClass.toClass(); Object obj = clazz.newInstance(); Method m = clazz.getDeclaredMethod("main", String[].class); m.invoke(obj, (Object)new String[]{}); }} javassist常见的用法

javassist中常用的类:

ClassPool可以理解为一个java类池,里面有一个Hashtable存放着所有创建的类和加载过得类,加载的类用到的才会存放到这个Hashttable里面。

CtClass表示一个java类封装的对象

CtMethod表示一个方法

CtField表是一个属性

CtConstructor表示构造方法

javassist的api还是比较方便使用的,如果创建一个类如上一个例子所示classPool.makeClass(“name”);

最后输出的话就是ctClass.writeFile()。也可以输出到bytecode用自定义的类加载器加载。

CtMethod 和 CtConstructor 提供了 insertBefore(),insertAfter() 和 addCatch() 方法。 它们可以将用 Java 编写的代码片段插入到现有方法中。也可以按行插入,使用insertAt()。

insertBefore() ,insertAfter() ,addCatch() 和 insertAt() 中的String对象是可以使用占位符的,如果说想获取当前方法的参数,可以用$$来表示,还有其他的:

符号含义$0, $1, $2, …this and 方法的参数$args方法参数数组.它的类型为 Object[]$$所有实参。例如, m($$) 等价于 m($1,$2,…)$cflow(…)cflow 变量$r返回结果的类型,用于强制类型转换$w包装器类型,用于强制类型转换$_返回值$sig类型为 java.lang.Class 的参数类型数组$type一个 java.lang.Class 对象,表示返回值类型$class一个 java.lang.Class 对象,表示当前正在修改的类$process方法的名称

目前大多数常用的操作差不1多就是这些了,其他详细的教程参考文章最后的参考文档。

使用javassist进行字节码插桩的一个demo

我们要进行字节码插桩进行方法监控和调用链追踪,其实就是在运行的时候修改字节码,把我们想调用的方法插入到字节码里面去,也就是在方法开始结束和抛异常的时候调用我们的方法,也就是我们上一节中定义的Spy方法,至于具体的逻辑则需要到Spy方法中进行,如果发送到消息队列,或者存到数据库等操作。

下面我们简单写一个demo,把我们需要做的事情用几十行代码来讲清楚:

public static void main(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); // 创建类,类的全类名 CtClass ctClass = cp.makeClass("com.github.blocks.bytecode.javassit.HelloWorld"); CtMethod main = new CtMethod(CtClass.voidType, "main", new CtClass[] {cp.get(String[].class.getName())}, ctClass); main.setModifiers(Modifier.PUBLIC + Modifier.STATIC); main.setBody("{System.out.println("hello world");}"); ctClass.addMethod(main); // 需要把原方法复制一个出来,因为原方法可能有多个返回值,所以我们想存返回值的时候比较麻烦,复制一个方法,在原方法调用这个方法,则比较好的解决了这个问题。 CtMethod copyMain = CtNewMethod.copy(main, ctClass, null); copyMain.setName(main.getName() + "$"); ctClass.addMethod(copyMain); System.out.println(main.getSignature()); main.setBody("{main$($$);}"); main.addCatch("{ throw $e;}", cp.get(IOException.class.getName())); main.addLocalVariable("start", cp.get(long.class.getName())); main.insertBefore("{ start = System.currentTimeMillis();}"); main.insertAfter("{System.out.println(System.currentTimeMillis() - start);}"); // 创建无参数构造方法 CtConstructor ctConstructor = new CtConstructor(new CtClass[] {}, ctClass); ctConstructor.setBody("{}"); ctClass.addConstructor(ctConstructor); // 输出类内容,可以加上路径,会输出class文件 ctClass.writeFile("xx"); // 测试调用 Class<?> clazz = ctClass.toClass(); Object obj = clazz.newInstance(); Method m = clazz.getDeclaredMethod("main", String[].class); m.invoke(obj, (Object) new String[] {});}

最后修改后的字节码反编译后的类变成了这样:

public class HelloWorld { public static void main(String[] var0) { long start = System.currentTimeMillis(); try { main$(var0); } catch (IOException var6) { throw var6; } Object var5 = null; System.out.println(System.currentTimeMillis() - start); } public static void main$(String[] var0) { System.out.println("hello world"); } public HelloWorld() { }}

这里需要注意的点有以下几个:

我们需要复制原来的方法,原方法有多个返回值的时候比较方便处理。调用原来方法的方式是{main$($$);},其中$$表示当前方法的参数。添加try catch的方法就是addCatch方法,catch里面做的事情就是方法参数,这里我是捕获异常之后又throw了出来,捕获的异常使用$e来表示。

一个基本的字节码插桩进行方法监控的demo就是这些了,到时候无非就是把一些位置的代码换成我们的Spy方法,但是其实整个项目其中的一部分核心功能就是这些了。

如果您喜欢,可以点个赞和关注,十分感谢。

参考

官方文档:https://github.com/jboss-javassist/javassist/wiki

官方文档翻译:https://www.jianshu.com/p/43424242846b

如果你喜欢,欢迎点赞关注加收藏,后续我还会继续做一些类似的有意思的java玩具。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。