logo头像
书院的十三先生

Java调用链跟踪关键技术(二)Javaagent

一、Javaagent

网上关于Javaagent的介绍很多,请找度娘和谷兄。唯一提的一点是字节码注入比较好用的是bytebuddy,封装度很高,使用简单。

二、代码样例

以下为关键代码样例,可以依样画瓢自行改造。
1.编写agent入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.javashizhan.trace;

import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isSetter;
import static net.bytebuddy.matcher.ElementMatchers.nameContainsIgnoreCase;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWithIgnoreCase;
import static net.bytebuddy.matcher.ElementMatchers.not;

import java.lang.instrument.Instrumentation;

import com.javashizhan.trace.interceptor.AbstractJunction;
import com.javashizhan.trace.interceptor.ProtectiveShieldMatcher;
import com.javashizhan.trace.interceptor.TraceInterceptor;

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

public class TraceAgent {

public static void premain(String arguments, Instrumentation instrumentation) {
new AgentBuilder.Default()
.type(buildMatch())
.transform((builder, type, classLoader, module) ->
builder.method(ElementMatchers.any())
.intercept(MethodDelegation.to(TraceInterceptor.class)) // 拦截器
).installOn(instrumentation);
}

public static ElementMatcher<? super TypeDescription> buildMatch() {
ElementMatcher.Junction judge = new AbstractJunction<NamedElement>() {
@Override
public boolean matches(NamedElement target) {
return true;
}
};
judge = judge.and(not(isInterface())).and(not(isSetter()))
.and(nameStartsWithIgnoreCase("io.spring"))
.and(not(nameContainsIgnoreCase("util")))
.and(not(nameContainsIgnoreCase("interceptor")));
judge = judge.and(not(isSetter()));
return new ProtectiveShieldMatcher(judge);
}

}

2.拦截器类TraceInterceptor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.javashizhan.trace.interceptor;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;

import com.javashizhan.trace.domain.CallMethod;
import com.javashizhan.trace.TraceWrapper;
import com.javashizhan.trace.collector.DBCollector;
import com.javashizhan.trace.domain.Trace;
import com.javashizhan.trace.domain.TraceRecord;

import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

public class TraceInterceptor {

@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable) throws Exception {

before(method);

try {
return callable.call();
} finally {
after();
}
}

public static void after() {
Trace trace = TraceWrapper.getTrace(); //Trace类,可自行实现,不是关键

if (null != trace) {
if (trace.callMethodSize() > 0) {

CallMethod callMethod = trace.pop();

if (null != callMethod && callMethod.isTraceFlag()) {

callMethod.calculateCostTime();
trace.addTraceRecord(new TraceRecord(callMethod));

}

if (trace.callMethodSize() == 0) {
List<TraceRecord> traceRecordList = trace.getAllTraceRecord();
if (null != traceRecordList && traceRecordList.size() > 0) {
DBCollector collector = new DBCollector(traceRecordList);
new Thread(collector).start();
TraceWrapper.destory();
}
}
}
}
}

private static void before(Method method) {
Trace trace = TraceWrapper.getTrace();

CallMethod callMethod = new CallMethod(method);
if (isInnerClass(callMethod)) { //spring中有很多内部类,可以去掉
callMethod.setTraceFlag(false);
} else {
callMethod.setTraceFlag(true);
}

//不管是否跟踪都放进去
trace.push(callMethod);
}

private static boolean isInnerClass(CallMethod callMethod) {
return callMethod.getClassName().indexOf('$') > -1;
}


}

3.AbstractJunction.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.javashizhan.trace.interceptor;

import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatcher.Junction;
import net.bytebuddy.matcher.ElementMatcher.Junction.Conjunction;
import net.bytebuddy.matcher.ElementMatcher.Junction.Disjunction;

public abstract class AbstractJunction<V> implements ElementMatcher.Junction<V> {
@Override
public <U extends V> Junction<U> and(ElementMatcher<? super U> other) {
return new Conjunction<U>(this, other);
}

@Override
public <U extends V> Junction<U> or(ElementMatcher<? super U> other) {
return new Disjunction<U>(this, other);
}
}

4.ProtectiveShieldMatcher.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.javashizhan.trace.interceptor;

import net.bytebuddy.matcher.ElementMatcher;

public class ProtectiveShieldMatcher<T> extends ElementMatcher.Junction.AbstractBase<T> {

private final ElementMatcher<? super T> matcher;

public ProtectiveShieldMatcher(ElementMatcher<? super T> matcher) {
this.matcher = matcher;
}

public boolean matches(T target) {
try {
return this.matcher.matches(target);
} catch (Throwable t) {
//logger.warn(t, "Byte-buddy occurs exception when match type.");
return false;
}
}
}

三、pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>trace</groupId>
<artifactId>chain</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<!-- <spring-cloud.version>Finchley.SR1</spring-cloud.version> -->
</properties>

<dependencies>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<!-- <Premain-Class>com.undergrowth.secure.SecurityAgent</Premain-Class> -->
<!-- <Premain-Class>com.undergrowth.agent.AgentToString</Premain-Class>-->
<Premain-Class>com.javashizhan.trace.TraceAgent</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

四、在Java应用中添加启动参数

1.先将agent工程打成jar包
2.在要使用agent的Java应用中添加如下VM启动参数

1
-javaagent:D:\MyApp\apache-skywalking-apm-bin\agent\chain-0.0.1-SNAPSHOT.jar

注意自行替换jar包路径。

end.


微信公众号:


加入《Java栈实战营》知识星球,参与讨论,更多实战代码分享!
https://t.zsxq.com/RNzfi2j