Btrace简介
- Btrace可以动态地向目标应用程序的字节码注入追踪代码
- 用到的技术:JavaComplierApi、JVMTI、Agent、Instrumentation+ASM(相对来说都是底层的技术)
Btrace的gitbub地址
github地址
点击Release Page之后进入到一个页面
然后下载对应的版本。linux下载btrace-bin-1.3.11.3.tgz,windows下载btrace-bin-1.3.11.3.zip
新建Btrace的环境变量
新建环境变量 BTRACE_HOME
添加Path:%BTRACE_HOME%\bin
两种运行脚本方式
- 在JVisualVM中添加Btrace插件,添加classpath
- 使用命令行btrace <trace_script>
编写将要被拦截的Controller
在Ch4Controller类中定义了一个arg1方法
package com.imooc.monitor_tuning.chapter4;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ch4")
public class Ch4Controller {
@RequestMapping("/arg1")
public String arg1(@RequestParam String name){
return "hello," + name;
}
}
如何编写btrace脚本(trace_script)
btrace脚本和java代码长得非常像。btrace非常像平常我们所说的拦截器。
这里拦截Ch4Controller类中定义的arg1方法
package com.imooc.monitor_tuning.chapter4;
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;
@BTrace
public class PrintArgSimple {
/*
* 拦截Ch4Controller类的arg1方法,ENTRY表示在已进入到方法的时候进行拦截
* */
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
method="arg1",
location=@Location(Kind.ENTRY)
)
public static void anyRead(@ProbeClassName String pcn,@ProbeMethodName String pmn, AnyType[] args){
BTraceUtils.printArray(args);
BTraceUtils.println(pcn + "," + pmn);
BTraceUtils.println();
}
}
添加Btrace运行所需要的jar包
在下载下来的btrace软件中,有build目录,其中有我们需要用到的三个jar包:
btrace-agent.jan
btrace-boot.ja
btrace-client.jar
将他们复制到IDEA中的一个文件夹中。然后进行添加jar包
依次在IDEA中点击 Project Structure->Project Settings->Modules->Dependencies,然后点击右侧的"+"号
然后点击第一个,选择存放jar包的那个文件夹,然后Apply,确定即可。
进行测试
输入jps -l查看进程的ID
使用btrace 进程ID filename来注入字节码,在浏览器中访问:http://localhost:12345/ch4/arg1?name=%22%E5%B0%8F%E6%98%8E%22 控制台就可输出:[“小明”,]
Btrace不仅仅拦截Controller,实际上,只要是一个java类,都可以进行拦截。
使用详解
- 拦截方法
- 拦截时机
- 拦截this、参数、返回值
- 其他
拦截方法
- 拦截普通方法 @OnMethod(clazz="",method="")
- 拦截拦截函数 @OnMethod(clazz="",method="")
为什么叫做呢,因为构造函数在字节码层面上叫做这个名字,因为是在字节码层面上进行的拦截 - 拦截同名函数,用参数区分
拦截User类的构造函数
编写btrace脚本:
PrintConstructor.java
package com.imooc.monitor_tuning.chapter4;
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintConstructor {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter2.User",//因为拦截的是User类的构造方法,所以这里填写User类
method="<init>" //因为是构造方法,在字节码层面,所有类的构造方法的名字都叫<init>
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args){
BTraceUtils.printArray(args);
BTraceUtils.println(pcn + "," + pmn);
BTraceUtils.println();
}
}
在controller中添加内容:
@RequestMapping("constructor")
public User constructor(User user){
return user;
}
进行测试
因为是编写的btrace脚本,所以不需要重启spring boot应用。
启动btrace脚本
在浏览器中进行访问
http://localhost:12345/ch4/constructor?name=“小明”&id=1
http://localhost:12345/ch4/constructor?name=小明&id=2
控制台输出
拦截同名的方法(多路重载)
在controller中添加两个同名的方法
@RequestMapping("/same1")
public String same(@RequestParam("name") String name){
return "hello," + name;
}
@RequestMapping("/same2")
public String same(@RequestParam("name") String name,@RequestParam("id") int id){
return "hello," + name + "," + id;
}
编写btrace脚本代码
package com.imooc.monitor_tuning.chapter4;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;
@BTrace
public class PrintSame {
@OnMethod(
clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",//因为这次拦截的是Controller中的某个方法
method="same"
)
public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn,String name,int id){
BTraceUtils.println(pcn + "," + pmn);
BTraceUtils.println("hello," + name + "," + id);
BTraceUtils.println();
}
}
在浏览器中进行访问
http://localhost:12345/ch4/same2?name=%E5%B0%8F%E6%98%8E&id=3