使用JavaAgent实现一个简单的需求:方法的运行时间。这是一个很简单的例子,我们的目的是了解各种库之间的差异,做出正确的选择。
我们将学习:
- 使用ASM, Javassist和byte-buddy库实现
- 编写测试用例,运行查看效果
- 设置环境变量切换实现
使用版本
- JDK 11
- asm 9.2
- javassist 3.28
- byte-buddy 1.12.3
我们要确定目标类,也就是哪些类需要处理。本项目确定一个目标类 TargetClass.java, 代码如下:
@Slf4j
public class TargetClass {
public void method1() throws InterruptedException{
log.info(">>> method1 called ");
Thread.sleep((new Random()).nextInt(1000));
}
public String method2(){
log.info(">>> method2 called ");
try {
Thread.sleep((new Random()).nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "end";
}
}
我们要了解这个类的方法运行时间,这里需要注意: method1 没有返回值。字节码编程时,这两个方法的处理有所不同。
我们还需要程序入口,并且创建了 TargetClass 实例,运行method1和method2方法,Main.java代码如下:
@Slf4j
public class Main {
public static void main(String[] args) throws InterruptedException {
log.info(">>> Main is running -> {}", Main.class.getName());
TargetClass target = new TargetClass();
target.method1();
target.method2();
}
}
运行程序,可以看见目标方法的执行情况。
字节码编程我们需要一个功能统计运行时间,秒表(StopWatch.java)实现如下:
@Slf4j
public class StopWatch {
public static class StaticClazz {
static ThreadLocal t = new ThreadLocal();
public static void start() {
t.set(System.currentTimeMillis());
}
public static void end() {
final long elapseOfTime = System.currentTimeMillis() - t.get();
log.info("{} elapse of time: {}", Thread.currentThread().getStackTrace()[2] , elapseOfTime);
t.remove();
}
}
public static class Clazz {
private final String methodName;
private long start;
public Clazz(String methodName) {
this.methodName = methodName;
}
public void start() {
start = System.currentTimeMillis();
}
public void end() {
final long elapseOfTime = System.currentTimeMillis() - start;
log.info("{} elapse of time: {}", methodName , elapseOfTime);
}
}
}
这里有两种实现:类和静态类,为什么呢?字节码编程是很专业的,需要学习库的语法,使用静态类可以降低实现需求的难度。
我们先实现需求,对目标类方法编程,ASM库实现(TransformerWithASM.java):
public class TransformerWithASM implements Transformer{
private final static String TARGET_CLASS = "io/github/kavahub/learnjava/TargetClass";
@Override
public void transform(String args, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
log.info("transform class - " + className);
// 仅处理TargetClass类
if (className.equals(TARGET_CLASS)) {
ElapseOfTimeClassWriter writer = new ElapseOfTimeClassWriter(classfileBuffer);
return writer.perform();
}
return ClassFileTransformer.super.transform(loader, className, classBeingRedefined, protectionDomain,
classfileBuffer);
}
});
}
public static class ElapseOfTimeClassWriter {
private final ClassReader reader;
private final ClassWriter writer;
public ElapseOfTimeClassWriter(byte[] contents) {
reader = new ClassReader(contents);
writer = new ClassWriter(reader, 0);
}
public byte[] perform() {
ElapseClassAdapter elapseClassAdapter = new ElapseClassAdapter(writer);
reader.accept(elapseClassAdapter, 0);
return writer.toByteArray();
}
}
public static class ElapseClassAdapter extends ClassVisitor {
public ElapseClassAdapter(ClassVisitor classVisitor) {
super(ASM7, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
return new ElapseMethodAdapter(methodVisitor);
}
}
public static class ElapseMethodAdapter extends MethodVisitor {
private final static String owner = "io/github/kavahub/learnjava/StopWatch$StaticClazz";
public ElapseMethodAdapter(MethodVisitor methodVisitor) {
super(ASM7, methodVisitor);
}
@Override
public void visitCode() {
// 方法开始时,插入StopWatch代码,调用start方法
mv.visitMethodInsn(INVOKESTATIC, owner, "start", "()V", false);
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
if ((opcode >= IRETURN && opcode <= RETURN)) {
// 方法返回时, 插入StopWatch代码,调用end方法
visitMethodInsn(INVOKESTATIC, owner, "end", "()V", false);
}
mv.visitInsn(opcode);
}
}
}
为了降低实现难度,我们使用了秒表(StopWatch)的静态类。
Javassist实现
public class TransformerWithJavassist implements Transformer {
private final static String TARGET_CLASS = "io/github/kavahub/learnjava/TargetClass";
@Override
public void transform(String args, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
log.info("transform class - {}" + className);
// 仅处理TargetClass类
if (className.equals(TARGET_CLASS)) {
ElapseOfTimeClassWriter writer = new ElapseOfTimeClassWriter(className);
return writer.write();
}
return ClassFileTransformer.super.transform(loader, className, classBeingRedefined, protectionDomain,
classfileBuffer);
}
});
}
public static class ElapseOfTimeClassWriter {
private final static String STOPWATCH_START = "n final io.github.kavahub.learnjava.StopWatch.Clazz stopwatch = new io.github.kavahub.learnjava.StopWatch.Clazz("%s");n"
+ "n stopwatch.start();n";
private final static String STOPWATCH_END = "n stopwatch.end();n";
private final String className;
public ElapseOfTimeClassWriter(String className) {
this.className = className.replace("/", ".");
}
public byte[] write() {
try {
return wirte0();
} catch (NotFoundException | CannotCompileException | IOException e) {
throw new RuntimeException(e);
}
}
private byte[] wirte0() throws NotFoundException, CannotCompileException, IOException {
CtClass ctclass = ClassPool.getDefault().get(className);
for (CtMethod ctMethod : ctclass.getDeclaredMethods()) {
CtClass returnType = ctMethod.getReturnType();
// 无返回值方法
if (CtClass.voidType.equals(returnType)) {
String methodName = ctMethod.getName();
// 新定义一个方法
String newMethodName = "elapse$" + methodName;
// 将原来的方法名字修改
ctMethod.setName(newMethodName);
// 创建新的方法,复制原来的方法,名字为原来的名字
CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName, ctclass, null);
// 构建新的方法体
StringBuilder bodyStr = new StringBuilder();
bodyStr.append("{");
bodyStr.append(String.format(STOPWATCH_START, methodName));
// 调用原有代码,类似于method();($$)表示所有的参数
bodyStr.append(newMethodName + "($$);n");
bodyStr.append(STOPWATCH_END);
bodyStr.append("}");
// 替换新方法
newMethod.setBody(bodyStr.toString());
// 增加新方法
ctclass.addMethod(newMethod);
}
}
// 修改后的方法列表 会发现多了一个方法
log.info(" after update method list ...");
for (CtMethod ctMethod : ctclass.getDeclaredMethods()) {
log.info("Method name - {}", ctMethod.getName());
}
return ctclass.toBytecode();
}
}
}
byte-buddy 实现
public class TransformerWithByteBuddy implements Transformer {
private final static String TARGET_CLASS = "io.github.kavahub.learnjava.TargetClass";
private AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
return builder
// 拦截任意方法
.method(ElementMatchers.any())
// 委托
.intercept(MethodDelegation.to(ElapseOfTimeWriter.class));
};
private AgentBuilder.Listener listener = new AgentBuilder.Listener() {
@Override
public void onDiscovery(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
log.info("onDiscovery - {}", typeName);
}
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module,
boolean loaded, DynamicType dynamicType) {
log.info("onTransformation - {}", typeDescription);
}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module,
boolean loaded) {
log.info("onIgnored - {}", typeDescription);
}
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded,
Throwable throwable) {
log.info("onError - {}", typeName);
}
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded) {
log.info("onComplete - {}", typeName);
}
};
@Override
public void transform(String args, Instrumentation inst) {
new AgentBuilder.Default()
// 指定需要拦截的类
.type(ElementMatchers.named(TARGET_CLASS))
.transform(transformer)
.with(listener)
.installOn(inst);
}
public static class ElapseOfTimeWriter {
@RuntimeType
public static Object wirte(@Origin Method method, @SuperCall Callable> callable) throws Exception {
final StopWatch.Clazz stopWatch = new StopWatch.Clazz(method.getName());
stopWatch.start();
try {
// 原有函数执行
return callable.call();
} finally {
stopWatch.end();
}
}
}
}
三个实现类中,都有ElapseOfTimeWriter静态类,这个类的就是字节码编程,实现需求的。
我们有三种实现,用户可以选择其中的任意一种。如果实现这个功能,需要实现Provider(提供者),使用环境变量决定使用哪种实现,TransformerProvider.java 代码如下:
public class TransformerProvider implements Supplier{ public final static String CLASS_FILE_TRANSFORMER_KEY = "transformer_class"; private final String CLASS_FILE_TRANSFORMER_VALUE; public final static TransformerProvider INSTANCE = new TransformerProvider(); private TransformerProvider() { CLASS_FILE_TRANSFORMER_VALUE = System.getenv(CLASS_FILE_TRANSFORMER_KEY); } @Override public Transformer get() { String className = CLASS_FILE_TRANSFORMER_VALUE; if (className == null) { // 默认 className = TransformerWithASM.class.getName(); } // 反射创建 Class> clazz = null; try { clazz = Class.forName(className); if (Transformer.class.isAssignableFrom(clazz)) { return (Transformer)clazz.getDeclaredConstructor().newInstance(); } log.error("Is not a correct subclass of ClassFileTransformer -> {}", className); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { log.error("Fail to create object", e); } return null; } }
需求已经完毕执行完成。现在我们要与JavaAgent集成了,PremainAgent.java代码如下:
@Slf4j
public class PremainAgent {
public static void premain(String args, Instrumentation inst){
log.info("Agent called - {}", PremainAgent.class.getName());
Transformer transformer = TransformerProvider.INSTANCE.get();
if (transformer != null) {
transformer.transform(args, inst);
log.info("Transformer registered successfully -> {}", transformer.getClass().getName());
} else {
log.warn("Agent failure, because of transformer is not configured correctly");
}
}
}
集成完成了。我们使用maven-jar-plugin插件打包项目,POM配置如下:
org.apache.maven.plugins maven-jar-plugintrue io.github.kavahub.learnjava.PremainAgent true true
运行Maven命令打包:
mvn clean install
在 target 目录下,生成 elapse-of-time.jar
如何运行通常使用命令行,格式如下:
java -javaagent:path/to/elapse-of-time.jar -cp %classpath% io.github.kavahub.learnjava.Main
上面只是格式,不是最终的命令。其中classpath最繁琐。
我们使用测试用例的方式:在测试种运行命令行。需要增加Main.java的功能:
@Slf4j
public class Main {
......
public static ProcessBuilder build() throws IOException, InterruptedException {
ProcessBuilder builder = new ProcessBuilder();
addJavaBin(builder);
// 注意:javaagent要放在前面
addJavaAgent(builder);
addClasspath(builder);
addClassMain(builder);
builder.inheritIO();
return builder;
}
private static void addClassMain(ProcessBuilder builder) {
String className = Main.class.getCanonicalName();
builder.command().add(className);
}
private static void addClasspath(ProcessBuilder builder) {
String classpath = System.getProperty("java.class.path");
builder.command().add("-cp");
builder.command().add(classpath);
}
private static void addJavaAgent(ProcessBuilder builder) {
Path javaagent = Paths.get("target", "elapse-of-time.jar");
builder.command().add("-javaagent:" + javaagent.toAbsolutePath().toString());
}
private static void addJavaBin(ProcessBuilder builder) {
String javaHome = System.getProperty("java.home");
String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
builder.command().add(javaBin);
}
}
测试用例 TargetClassManualTest.java 代码如下:
public class TargetClassManualTest {
@Test
public void givenDefault_whenRunProgream_thenCheckConsole() throws IOException, InterruptedException {
// 启动程序
ProcessBuilder builder = Main.build();
Process process = builder.start();
// 等待程序运行完成
while(process.isAlive()) {}
}
@Test
public void givenJavassist_whenRunProgream_thenCheckConsole() throws IOException, InterruptedException {
// 启动程序
ProcessBuilder builder = Main.build();
builder.environment().put("transformer_class", "io.github.kavahub.learnjava.TransformerWithJavassist");
Process process = builder.start();
// 等待程序运行完成
while(process.isAlive()) {}
}
@Test
public void givenASM_whenRunProgream_thenCheckConsole() throws IOException, InterruptedException {
// 启动程序
ProcessBuilder builder = Main.build();
builder.environment().put("transformer_class", "io.github.kavahub.learnjava.TransformerWithASM");
Process process = builder.start();
// 等待程序运行完成
while(process.isAlive()) {}
}
@Test
public void givenByteBuddy_whenRunProgream_thenCheckConsole() throws IOException, InterruptedException {
// 启动程序
ProcessBuilder builder = Main.build();
builder.environment().put("transformer_class", "io.github.kavahub.learnjava.TransformerWithByteBuddy");
Process process = builder.start();
// 等待程序运行完成
while(process.isAlive()) {}
}
}
我们可以愉快的运行测试了,抛弃了繁琐的命令行。
最后全部的代码,可以在这里查看elapse-of-time ,欢迎顶赞,感谢!



