【JavaAgent】字节码编程 - 方法运行时间实现

简介

使用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 {
    /**
     * 
     * 静态方法实现
     *
     * @author PinWei Wan
     * @since 1.0.1
     */
    public static class StaticClazz {
        static ThreadLocal<Long> t = new ThreadLocal<Long>();
    
        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();          
        }
    
    }

    /**
     * 
     * 类实现
     *
     * @author PinWei Wan
     * @since 1.0.1
     */
    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<Transformer> {
    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配置如下:

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Premain-Class>
                                io.github.kavahub.learnjava.PremainAgent
                            </Premain-Class>

                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>

运行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 {
    
    ......

    /**
     * 命令行运行,测试使用
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    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 ,欢迎顶赞,感谢!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值