准备
前面我们已经通过相关知识知道一般APM项目都是通过javaagent来对原项目代码进行一些改造以实现调用链需要的相关操作-- 比如加入上下文,添加必要的调用监控数据。
我们的主要开发工具就是ByteBuddy
注意,本系列相关文章涉及的源码放在了github上,有些代码不会贴出,请查看源码。地址:https://github.com/YounianC/APMSample
整个文章由于不停修改代码,为避免不知道当前文章所处代码阶段,一般会注明阶段Tag:XXX
。
首先实现javaagent,在main方法之前执行。
- 创建一个maven项目
- 创建类Agent作为javaagent入口类
public class Agent {
public static void premain(String agentArgs, Instrumentation instrumentation){
System.out.println("Enter premain.....");
}
}
- 并在POM 添加manifest信息
<build>
<finalName>APMAgent</finalName>
<plugins>
<plugin>
<!-- https://maven.apache.org/plugins/maven-shade-plugin/shade-mojo.html -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>${premain.class}</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
- mvn构建一下,到target目录查看有APMAgent.jar即可
- 随便写一个程序,只有一个main方法都行,然后在启动参数VMoptions中加上
-javaagent:path\APMSample\target\APMAgent.jar


- 运行发现控制台打印出了
Enter premain.....
即可。
到这里我们已经使用javaagent能够实现在main方法之前执行一些操作,但是到目前为止没有任何的功能。那么接下来使用ByteBuddy来进行一些更多的操作。
添加ByteBuddy进行高级操作,阶段Tag初步实现ByteBuddy拦截类
- 在pom.xml文件中添加dependency
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.5.7</version>
</dependency>
- premain中添加以下代码:
new AgentBuilder.Default().type(nameStartsWith("younian.apmsample.agent.test")).transform(new AgentBuilder.Transformer() {
@Override
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader) {
System.out.println("transform...");
return builder;
}
}).with(new AgentBuilder.Listener() {
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module,
DynamicType dynamicType) {
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
}
}).installOn(instrumentation);
这个操作的目的就是用nameStartsWith("younian.apmsample.agent.test")
限定的类在加载时将经过ByteBuddy的一个transform
操作,具体操作是public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader)
的内容,现在重新package以下agent,然后执行新加的APMAgentTest中的Test类的main方法(注意依然需要加上javaagent参数)。
可以看到输出:
Enter premain.....
transform...
main.....
现在已经实现能够通过ByteBuddy拦截指定类,这里拦截类的过滤方法是使用的nameStartWith,其实在net.bytebuddy.matcher.ElementMatchers
类下包括很多的其他限定,几种常用的限定包括:
- named(ClassName) 精确限定某个类
- nameStartsWith、nameEndsWith 限定一系列类
- isStatic 等限定
- annotationType 通过注解限定(比如被
@controller
标注的类)
- takesArgument(s) 通过参数个数和类型限定
- is、or、not等逻辑判断限定
- 等其他
- 添加transform方法:
ClassInstanceMethodInterceptor methodInterceptor = new ClassInstanceMethodInterceptor();
ElementMatcher matcher = named("test1").or(named("test2")).or(named("testStatic"));
builder = builder.method(not(isStatic()).and(matcher)).intercept(MethodDelegation.to(methodInterceptor));
这里的主要含义是:将不是静态方法的且(函数名为test1、test2)的函数拦截,并且委托给一个ClassInstanceMethodInterceptor 实例执行。
2. 添加ClassInstanceMethodInterceptor类:
public class ClassInstanceMethodInterceptor {
@RuntimeType
public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @Origin Method method,
@SuperCall Callable<?> zuper) throws Throwable {
System.out.println("intercept " + method.getName());
Object result = null;
try {
result = zuper.call();
} catch (Throwable t) {
t.printStackTrace();
}
return result;
}
}
这里的intercept
方法就是委托之后的具体调用方法。调用时将不会再直接调用方法,而是调用上面的委托方法。zuper.call()
是指调用被拦截函数病返回。这个拦截方法只是在函数执行之前加了一行打印当前拦截的方法名
3. 修改Test类方法:
public class Test {
public static void main(String[] args) {
Test.testStatic();
Test test = new Test();
test.test1();
test.test2();
test.test3();
}
public void test1(){
System.out.println("test1");
}
public void test2(){
System.out.println("test2");
}
public void test3(){
System.out.println("test3");
}
public static void testStatic(){
System.out.println("testStatic");
}
}
- 执行main方法(依然需要javaagent)
Enter premain.....
transform...
testStatic
intercept test1
test1
intercept test2
test2
test3
根据打印可以发现,只有test1
和test2
被拦截了,原因是test3
没加入match且testStatic
方法是静态方法而我们限制了not(isStatic()
。
结语
其实到这里,基本的雏形已经出来了,具备了拦截具体方法并且加入自定义操作的能力;
试想一下:
- 如果能够拦截一个tomcat的类,这个类是分发所有用户请求的类
- 我们在intercept方法中加入一些信息,比如:请求时间、请求内容、请求IP、本服务器IP等。并且将这些信息发送给某个服务器进行统计汇总。
那么我们是不是就有了监控tomcat请求的能力?