JVM中的"AOP",java agent
前言:
考虑一下问题:如何统计一个方法执行了多久?我们会回答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字节码系列