前言:
考虑一下问题:如何统计一个方法执行了多久?我们会回答Spring aop代理,加拦截。但是这有一个问题,如果spring内部方法调用,aop就会失效了。
再想一想:我们代码写好了,或者我们的项目发布了,怎么统计方法运行时长,怎么能排查时间过长的方法执行?
带着这些问题,我们来看一下jvm层面的代理。
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字节码系列