简介
假设我们有需求:所有的控制器(Controller)添加耗时,对所有的服务(Service)添加调用信息,使用一个agent代理,也就是所有实现在一个jar包中,该如何处理?
此类问题,最好采用插件化开发:定义一个插件接口,各个功能实现接口即可。本文讲解如何定义接口,接口与byte-buddy集成,不涉及具体功能实现。
使用版本
- JDK 11
- byte-buddy 1.12.3
开发项目
要定义接口,必须先了解byte-buddy如何使用,下面是一个简单的例子:
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
return builder
// 拦截任意方法
.method(ElementMatchers.any()) <<1>>
// 委托
.intercept(MethodDelegation.to(ElapseOfTimeWriter.class)); <<2>>
};
new AgentBuilder.Default()
// 指定需要拦截的类
.type(ElementMatchers.named(TARGET_CLASS)) <<3>>
.transform(transformer) <<4>>
.with(listener) <<5>>
.installOn(inst);
首先创建Transformer实例:
<<1>> 定义需要拦截的方法,参数是ElementMatcher<MethodDescription> 实例。本需求就是所有的方法
<<2>> 拦截功能实现类,参数是Class<?>实例,也就是一个普通的类,但这个类必须使用@Advice注解
然后创建AgentBuilder实例:
<<3>>定义需要拦截的类,参数是ElementMatcher<TypeDescription>实例。本需求有两个,一个是Controller,一个是Service类
<<4>> Transformer实例
<<5>> 监听,参数是Listener实例。监听是全局的,只需要实例化一次就可以了,所以不应该包含在接口中
由以上分析可知,<<1>>,<<2>>, <<3>> 都是接口必须有的,子类必须实现的。经过探索,定义如下两个接口:
/**
* 目标提供者,需要处理的类及方法
*
* @author PinWei Wan
* @since 1.0.1
*/
public interface TargetElementProvider {
// 监控方法描述
ElementMatcher<MethodDescription> methodDescription();
// 监控类型描述
ElementMatcher<TypeDescription> typeDescription();
}
/**
* 代理插件
*
* @author PinWei Wan
* @since 1.0.1
*/
public interface Plugin {
// 名称
String name();
// 提供者
TargetElementProvider[] elementMatchers();
// 织入类
Class<?> advice();
}
在插件接口中,elementMatchers返回是数组,也就是插件子类可以提供多个匹配规则。
那么与byte-buddy集成就是简单了,代码如下:
public static void premain(String agentArgs, Instrumentation inst) {
log.info("Agent called - {}", PremainAgent.class.getName());
AgentBuilder agentBuilder = new AgentBuilder.Default();
// 扫描所有的插件
List<Plugin> plugins = PluginFactory.pluginGroup();
for (Plugin plugin : plugins) {
// 需要处理的类及方法
TargetElementProvider[] providers = plugin.elementMatchers();
for (TargetElementProvider provider : providers) {
AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
builder = builder.visit(Advice.to(plugin.advice()).on(provider.methodDescription()));
return builder;
};
agentBuilder = agentBuilder.type(provider.typeDescription()).transform(transformer);
}
}
// 监听
AgentBuilder.Listener listener = new AgentBuilder.Listener() {
@Override
public void onDiscovery(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader,
JavaModule javaModule, boolean b, DynamicType dynamicType) {
System.out.println("onTransformation - " + typeDescription);
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule,
boolean b) {
}
@Override
public void onError(String s, ClassLoader classLoader, JavaModule javaModule, boolean b,
Throwable throwable) {
}
@Override
public void onComplete(String s, ClassLoader classLoader, JavaModule javaModule, boolean b) {
}
};
agentBuilder.with(listener).installOn(inst);
}
具体功能实现,不是本文重点。如需要,参考文章 【JavaAgent】字节码编程 - 方法运行时间实现
最后
全部的代码,可以在这里查看 opentrace,欢迎顶赞,感谢!