ByteBuddy
一路上看见晦涩难懂的ASM,小巧可人的Javassist。直到遇见了ByteBuddy才知世上竟有如此的冷艳简洁。
JavaAgent
从Jdk1.5开始Java开始支持Java Agent特性,可以通过premain方法,在Class字节码加载进虚拟机之前对底层的字节码进行修改。从而达到可以自定义特性的功能。给Aop的实现提供了一种更加简洁的方式。
ByteBuddy
字节码修改工具貌似从Java的诞生就一直存在,一开始的ASM,后来可以通过人类可以理解的方式修改字节码的Javassist,到现在的ByteBuddy,甚至不需要了解Class文件的构成就可以修改字节码,实现自己需求,简直神器。对于Class文件结构构成不在此赘述,有兴趣可以去之前摹写的一个加载Class文件的工具了解一下GOM,ByteBuddy官方地址。
通过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;
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;
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;
public class KMP { private static final String searchStr = "bacbababadababacambabacaddababacasdsd"; private static final String ptrStr = "ababaca";
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
|