ByteBuddy是一种字节码技术框架,其广泛用于中间件开发,用于字节码增强,变更字节码的形式来拦截,用途如:链路追踪,系统JVM状态监控,耗时分析等。目前市面上常见的链路追踪框架为:skywalking、美团cat等。本博客有对应skywalking解析
相关资料
官网:https://bytebuddy.net/
skywalking源码解析(不定期更新):https://gitee.com/lidishan/kywalking-source-code-analysis
ByteBuddy参考代码
public class Boot {
public static void main(String[] args) throws Exception {
// 1 基本用法
base();
// 2 耗时、入参出参
use();
// 3 使用委托实现抽象类方法并注入自定义注解信息
abstractAnnonation();
}
private static void abstractAnnonation() {
// -- 生成含有注解的泛型实现字类
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
// 创建复杂类型的泛型注解
.subclass(TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build())
// 添加类信息包括地址
.name(Repository.class.getPackage().getName().concat(".").concat("UserRepository"))
// 匹配处理的方法
.method(ElementMatchers.named("queryData"))
// 交给委托函数
.intercept(MethodDelegation.to(UserRepositoryInterceptor.class))
// 拦截对应注解
.annotateMethod(AnnotationDescription.Builder.ofType(RpcGatewayMethod.class)
.define("methodName", "queryData")
.define("methodDesc", "查询数据").build())
.annotateType(AnnotationDescription.Builder.ofType(RpcGatewayClazz.class)
.define("alias", "dataApi")
.define("clazzDesc", "查询数据信息")
.define("timeOut", 350L).build())
.make();
// 输出类信息到目标文件夹下
outputClazz(dynamicType.getBytes(), 4);
}
private static void use() throws Exception {
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
// 继承的类
.subclass(BizMethod.class)
// 被监控的方法
.method(ElementMatchers.named("queryUserInfo"))
// 具体监控实现类
.intercept(MethodDelegation.to(MonitorDemo.class))
.make();
// 加载类
Class<?> clazz = dynamicType.load(Boot.class.getClassLoader())
.getLoaded();
// 调用方法,测试监控效果
BizMethod bizMethod = new BizMethod();
System.out.println(bizMethod.queryUserInfo("10001", "Adhl9dkl"));
// !!!用反射调用才有效果,
// 那premain怎么结合bytebuddy使用?通过agentBuilder.with(listener).installOn(inst)绑定
clazz.getMethod("queryUserInfo", String.class, String.class).invoke(clazz.newInstance(), "10001", "Adhl9dkl");
}
private static void base() throws Exception {
// 1 第一次输出一个简单的结构体
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
// 定义继承的类
.subclass(Object.class)
// 定义命名空间
.name("com.bd")
.make();
// 输出类字节码
outputClazz(dynamicType.getBytes(), 1);
// 2 增加一些参数、属性信息
DynamicType.Unloaded<?> dynamicType2 = new ByteBuddy()
// 定义继承的类
.subclass(Object.class)
// 定义命名空间
.name("com.bd")
// 定义一个main方法,public权限,并且是static
.defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC)
// 定义参数
.withParameter(String[].class, "args")
// 定义一个局部变量为"Hello World!"
.intercept(FixedValue.value("Hello World!"))
.make();
outputClazz(dynamicType2.getBytes(), 2);
// 3 委托函数使用
DynamicType.Unloaded<?> dynamicType3 = new ByteBuddy()
// 定义继承的类
.subclass(Object.class)
// 定义命名空间
.name("com.bd")
// 定义一个main方法,public权限,并且是static
.defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC)
// 定义参数
.withParameter(String[].class, "args")
// 定义委托的类,委托调用Hi.main()方法
.intercept(MethodDelegation.to(Hi.class))
.make();
outputClazz(dynamicType3.getBytes(), 3);
// 加载类
Class<?> clazz = dynamicType3.load(Boot.class.getClassLoader()).getLoaded();
// 反射调用
clazz.getMethod("main", String[].class).invoke(clazz.newInstance(), (Object) new String[1]);
}
private static void outputClazz(byte[] bytes, Integer num) {
FileOutputStream out = null;
try {
String pathName = Boot.class.getResource("/").getPath() + "ByteBuddyHelloWorld_" + num + ".class";
out = new FileOutputStream(pathName);
System.out.println("类输出路径:" + pathName);
out.write(bytes);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != out) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
上面会调用对应拦截器进行处理,下面举例一个拦截器
上面调用了intercept(MethodDelegation.to(MonitorDemo.class)),会执行如下intercept()拦截方法
public class MonitorDemo {
/**
* @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 = System.currentTimeMillis();
Object resObj = null;
try {
// @SuperCall 调用原方法
resObj = callable.call();
return resObj;
} finally {
System.out.println("===============MonitorDemo======================");
System.out.println("@AllArguments获取所有参数:" + JSON.toJSON(args));
System.out.println("@Argument(0)获取第一个参数结果:" + uid);
System.out.println("@This当前对象spanId结果:" + ((BizMethod) thisObj).getSpanId());
System.out.println("@Super父类对象结果:" + parentObj.hashCode());
System.out.println("@Origin方法名称:" + method.getName());
System.out.println("@Origin入参个数:" + method.getParameterCount());
System.out.println("@Origin入参类型:" + method.getParameterTypes()[0].getTypeName() + "、" + method.getParameterTypes()[1].getTypeName());
System.out.println("@Origin出参类型:" + method.getReturnType().getName());
System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("===============MonitorDemo======================");
}
}