JVM中的"AOP",java agent

前言:

考虑一下问题:如何统计一个方法执行了多久?我们会回答Spring aop代理,加拦截。但是这有一个问题,如果spring内部方法调用,aop就会失效了。

再想一想:我们代码写好了,或者我们的项目发布了,怎么统计方法运行时长,怎么能排查时间过长的方法执行?

 

带着这些问题,我们来看一下jvm层面的代理。

首先,可以参考一下这个 

Arthas运行原理

类似的,我们可以用到里面的技术,Instrument和Attach API

 

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("this is an perform monitor agent.");

        // 添加 Transformer
        ClassFileTransformer transformer = new PerformMonitorTransformer();
        inst.addTransformer(transformer);
    }
}

package instrument;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class PerformMonitorTransformer implements ClassFileTransformer {

    private static final String PACKAGE_PREFIX = "instrument";

    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        try {
            String currentClassName = className.replaceAll("/", ".");

            // 仅仅提升这个包中的类
            if (!currentClassName.startsWith(PACKAGE_PREFIX)) {
                return null;
            }

            System.out.println("now transform: [" + currentClassName + "]");

            CtClass ctClass = ClassPool.getDefault().get(currentClassName);
            CtBehavior[] methods = ctClass.getDeclaredBehaviors();
            for (CtBehavior method : methods) {
                enhanceMethod(method); // 提升方法
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /* 提升方法 */
    private void enhanceMethod(CtBehavior method) throws Exception {
        if (method.isEmpty()) {
            return;
        }
        final String methodName = method.getName();
        if (methodName.equalsIgnoreCase("main")) { // 不提升main方法
            return;
        }

        ExprEditor editor = new ExprEditor() {
            @Override
            public void edit(MethodCall methodCall) throws CannotCompileException {
                methodCall.replace(genSource(methodName));
            }
        };
        method.instrument(editor);
    }

    /* 打入新的代码 */
    private String genSource(String methodName) {
        StringBuilder source = new StringBuilder();
        source.append("{")
                .append("long start = System.nanoTime();\n") // 前置增强: 打入时间戳
                .append("$_ = $proceed($$);\n") // 保留原有的代码处理逻辑
                .append("System.out.print(\"method:[" + methodName + "]\");").append("\n")
                .append("System.out.println(\" cost:[\" +(System.nanoTime() -start)+ \"ns]\");") // 后置增强
                .append("}");
        return source.toString();
    }
}

 

 

测试类

public class InstrumentTest {
    private void fun1() {
        System.out.println("this is fun 1.");
    }

    private void fun2() {
        System.out.println("this is fun 2.");
    }

    // add VM options: -javaagent:./first-instrument/target/my-agent.jar=first
    public static void main(String[] args) {
        InstrumentTest test = new InstrumentTest();
        test.fun1();
        test.fun2();

        AnotherTest test1 = new AnotherTest();
        test1.fun3();
        test1.fun4();
    }
}

运行日志

 

 简单的demo大概是这样 代码地址

参考

Java字节码系列

1:Java字节码1-Agent简单上手

2: Java字节码2-instrument初体验

3: Java字节码3-使用ByteBuddy实现一个Java-Agent

4: Java字节码4-使用Java-Agent实现一个JVM监控工具

posted @ 2021-09-22 14:56  _Phoenix  阅读(245)  评论(0编辑  收藏  举报