ByteBuddy是一种字节码技术框架,其广泛用于中间件开发,用于字节码增强,变更字节码的形式来拦截,用途如:链路追踪,系统JVM状态监控,耗时分析等。目前市面上常见的链路追踪框架为:skywalking、美团cat等。本博客有对应skywalking解析,链接为:/article/details/116092966
相关资料
官网:/
skywalking源码解析(不定期更新):/lidishan/kywalking-source-code-analysis
ByteBuddy参考代码
public class Boot {
public static void main(String[] args) throws Exception {
// 1 基本用法 /post/6844904151546069006
base();
// 2 耗时、入参出参 /post/6844904155102838792
use();
// 3 使用委托实现抽象类方法并注入自定义注解信息 /post/6844904159427166221
abstractAnnonation();
}
private static void abstractAnnonation() {
// -- 生成含有注解的泛型实现字类
<?> dynamicType = new ByteBuddy()
// 创建复杂类型的泛型注解
.subclass((, ).build())
// 添加类信息包括地址
.name(().getName().concat(".").concat("UserRepository"))
// 匹配处理的方法
.method(("queryData"))
// 交给委托函数
.intercept(())
// 拦截对应注解
.annotateMethod(()
.define("methodName", "queryData")
.define("methodDesc", "查询数据").build())
.annotateType(()
.define("alias", "dataApi")
.define("clazzDesc", "查询数据信息")
.define("timeOut", 350L).build())
.make();
// 输出类信息到目标文件夹下
outputClazz((), 4);
}
private static void use() throws Exception {
<?> dynamicType = new ByteBuddy()
// 继承的类
.subclass()
// 被监控的方法
.method(("queryUserInfo"))
// 具体监控实现类
.intercept(())
.make();
// 加载类
Class<?> clazz = (())
.getLoaded();
// 调用方法,测试监控效果
BizMethod bizMethod = new BizMethod();
(("10001", "Adhl9dkl"));
// !!!用反射调用才有效果,
// 那premain怎么结合bytebuddy使用?通过(listener).installOn(inst)绑定
("queryUserInfo", , ).invoke((), "10001", "Adhl9dkl");
}
private static void base() throws Exception {
// 1 第一次输出一个简单的结构体
<?> dynamicType = new ByteBuddy()
// 定义继承的类
.subclass()
// 定义命名空间
.name("")
.make();
// 输出类字节码
outputClazz((), 1);
// 2 增加一些参数、属性信息
<?> dynamicType2 = new ByteBuddy()
// 定义继承的类
.subclass()
// 定义命名空间
.name("")
// 定义一个main方法,public权限,并且是static
.defineMethod("main", , + )
// 定义参数
.withParameter(String[].class, "args")
// 定义一个局部变量为"Hello World!"
.intercept(("Hello World!"))
.make();
outputClazz((), 2);
// 3 委托函数使用
<?> dynamicType3 = new ByteBuddy()
// 定义继承的类
.subclass()
// 定义命名空间
.name("")
// 定义一个main方法,public权限,并且是static
.defineMethod("main", , + )
// 定义参数
.withParameter(String[].class, "args")
// 定义委托的类,委托调用()方法
.intercept(())
.make();
outputClazz((), 3);
// 加载类
Class<?> clazz = (()).getLoaded();
// 反射调用
("main", String[].class).invoke((), (Object) new String[1]);
}
private static void outputClazz(byte[] bytes, Integer num) {
FileOutputStream out = null;
try {
String pathName = ("/").getPath() + "ByteBuddyHelloWorld_" + num + ".class";
out = new FileOutputStream(pathName);
("类输出路径:" + pathName);
(bytes);
} catch (Exception e) {
();
} finally {
if (null != out) {
try {
();
} catch (IOException e) {
();
}
}
}
}
}
上面会调用对应拦截器进行处理,下面举例一个拦截器
上面调用了intercept(()),会执行如下intercept()拦截方法
public class MonitorDemo {
/**
* 参考:/post/6844904155102838792
* @RuntimeType 定义运行时的目标方法
*
* @SuperCall 用于调用父类版本的方法
* @AllArguments 绑定所有参数的数组
* @Argument 绑定单个参数. 0表示第一个参数
* @This 当前被拦截的、动态生成的那个对象
* @Super 当前被拦截的、动态生成的那个对象的父类对象
* @Origin 具备多种用法,如下:
* - Method:被调用的原始方法
* - Constructor:被调用的原始构造器
* - Class:当前动态创建的类
* - MethodHandle MethodType String 动态类的toString()的返回值 int 动态方法的修饰符
* @DefaultCall 调用默认方法而非super的方法
* @Super 注入父类型对象,可以是接口,从而调用它的任何方法
* @Empty 注入参数的类型的默认值
* @StubValue 注入一个存根值。对于返回引用、void的方法,注入null;对于返回原始类型的方法,注入0
* @FieldValue 注入被拦截对象的一个字段的值
* @Morph 类似于@SuperCall,但是允许指定调用参数
*/
@RuntimeType
public static Object intercept(@SuperCall Callable<?> callable,
@AllArguments Object[] args,
@Argument(0) Object uid,
@This Object thisObj,
@Super Object parentObj,
@Origin Method method)
throws Exception {
long start = ();
Object resObj = null;
try {
// @SuperCall 调用原方法
resObj = ();
return resObj;
} finally {
("===============MonitorDemo======================");
("@AllArguments获取所有参数:" + (args));
("@Argument(0)获取第一个参数结果:" + uid);
("@This当前对象spanId结果:" + ((BizMethod) thisObj).getSpanId());
("@Super父类对象结果:" + ());
("@Origin方法名称:" + ());
("@Origin入参个数:" + ());
("@Origin入参类型:" + ()[0].getTypeName() + "、" + ()[1].getTypeName());
("@Origin出参类型:" + ().getName());
("方法耗时:" + (() - start) + "ms");
("===============MonitorDemo======================");
}
}