神器之ByteBuddy,字节码注入分析代码执行性能


本文总阅读量

ByteBuddy

一路上看见晦涩难懂的ASM,小巧可人的Javassist。直到遇见了ByteBuddy才知世上竟有如此的冷艳简洁。

JavaAgent

从Jdk1.5开始Java开始支持Java Agent特性,可以通过premain方法,在Class字节码加载进虚拟机之前对底层的字节码进行修改。从而达到可以自定义特性的功能。给Aop的实现提供了一种更加简洁的方式。

ByteBuddy

字节码修改工具貌似从Java的诞生就一直存在,一开始的ASM,后来可以通过人类可以理解的方式修改字节码的Javassist,到现在的ByteBuddy,甚至不需要了解Class文件的构成就可以修改字节码,实现自己需求,简直神器。对于Class文件结构构成不在此赘述,有兴趣可以去之前摹写的一个加载Class文件的工具了解一下GOMByteBuddy官方地址

通过ByteBuddy修改字节码,计算方法耗时

直接上代码

  • 入口文件
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
package com.langel.anal.agent;

import com.langel.anal.agent.constant.Const;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import java.lang.instrument.Instrumentation;

/**
* @author lonelyangel.jcw@gmail.com
* @date 2019-07-04
*/
public class AnalAgent {

public static void premain(String agentOps, Instrumentation inst) {
System.out.println(Const.LOG_PREFIX + "This is an perform monitor agent.");
new Param(agentOps);
Param.print();
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader) {
return builder
.method(ElementMatchers.<MethodDescription>any()) // 拦截任意方法
.intercept(MethodDelegation.to(TimeInterceptor.class)); // 委托
}
};

AgentBuilder.Listener listener = 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) {
}

@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
}
};

String packagePrefix = Param.get("prefix") == null ? "com" : Param.get("prefix");
new AgentBuilder
.Default()
.type(ElementMatchers.nameStartsWith(packagePrefix)
.and(ElementMatchers.not(ElementMatchers.isAnnotation()))
.and(ElementMatchers.not(ElementMatchers.isInterface()))
.and(ElementMatchers.not(ElementMatchers.isEnum()))) // 指定需要拦截的类
.transform(transformer)
.with(listener)
.installOn(inst);
}

}
  • 拦截器文件
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
package com.langel.anal.agent;

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

import java.lang.reflect.Method;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Callable;

/**
* @author lonelyangel.jcw@gmail.com
* @date 2019-07-05
*/
public class TimeInterceptor {

@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable) throws Exception {
String methodName = method.getName();
if (methodName.startsWith("equals")
|| methodName.startsWith("hashCode")
|| methodName.startsWith("toString")) {
return callable.call();
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");


long start = System.currentTimeMillis();
try {
// 原有函数执行
return callable.call();
} finally {
Long elpse = System.currentTimeMillis() - start;
LogCachePool.putInstant(method.getDeclaringClass().getName(), methodName, elpse);
}
}
}

然后在JVMOptions加入下面配置

1
-javaagent:/Users/rick/Workspaces/sourcecode/anal/target/anal-agent.jar=file=log.txt,prefix=com.langel.kmp

这只是ByteBuddy 的API的一个简单使用。更多特性值得去探索

测试

  • 测试代码
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
package com.langel.kmp;

import com.langel.util.PrintUtils;

/**
* @Author: rick
* @Date: 2018/4/15 上午1:55
* @Description:
*/
public class KMP {
private static final String searchStr = "bacbababadababacambabacaddababacasdsd";
private static final String ptrStr = "ababaca";//-1,-1,0,1,2,-1,0

private static int[] calNext(String pStr) {
int[] next = new int[pStr.length()];
next[0] = -1;
int k = -1;
for (int i = 1; i < next.length; i++) {
while (k > -1 && pStr.charAt(i) != pStr.charAt(k + 1)) {
k = next[k];
}
if (pStr.charAt(k + 1) == pStr.charAt(i)) {
next[i] = k = k + 1;
}
}
return next;
}

private static int search(String sStr, String pStr) {
int[] next = calNext(pStr);
PrintUtils.println(next);
int k = -1;
for (int i = 0; i < sStr.length(); i++) {
while (k > -1 && sStr.charAt(i) != pStr.charAt(k + 1)) {
k = next[k];
System.out.println(k);
}
if (pStr.charAt(k + 1) == sStr.charAt(i)) {
k = k + 1;
System.out.println("k + 1 : " + k);
}
if (k == pStr.length() - 1) {
return i - pStr.length() + 1;
}
System.out.println();
}
return -1;
}

public static void main(String[] args) throws InterruptedException {
for (int idx = 0; idx < 1000; idx++) {
Thread.sleep(1000);
System.out.println(search(searchStr, ptrStr));
}

}
}

日志

1
2
3
4
Anal monitor : time - 2019-07-11T01:14:30.785
com.langel.kmp.KMP
-- search : 27ms
-- calNext : 0ms

目录
  1. 1. ByteBuddy
  2. 2. JavaAgent
  3. 3. ByteBuddy
  4. 4. 通过ByteBuddy修改字节码,计算方法耗时
  5. 5. 测试