个人随笔
目录
五、Java agent+ASM实战--监控所有方法执行时间
2020-12-24 11:06:14

我们知道agent技术可以在不入侵项目源代码的情况下来在项目初始化之前执行一些操作,而ASM技术可以直接修改字节码,两个结合起来就可以实现很多有意思的功能,比如监控所有方法的执行时间,代码如下。

1、代理类AgentTest

  1. public class AgentTest {
  2. /**
  3. * 在主程序运行之前执行
  4. * @param agentArgs
  5. * @param isnt
  6. */
  7. public static void premain(String agentArgs,Instrumentation inst) {
  8. // System.out.println("premain start");
  9. // System.out.println(agentArgs);
  10. inst.addTransformer(new AopAgentTransformer());
  11. }
  12. /**
  13. * 在主程序运行之后执行
  14. * @param args
  15. * @param inst
  16. */
  17. /* public static void agentmain(String args,Instrumentation inst) {
  18. System.out.println("loadagent after main run.args="+args);
  19. Class<?>[] classes = inst.getAllLoadedClasses();
  20. for (Class<?> cls : classes) {
  21. System.out.println(cls.getName());
  22. }
  23. System.out.println("agent run completely");
  24. }*/
  25. }

这里用的是在程序运行之前执行,因为我们要修改字节码,那么肯定要在主程序运行之前执行,在premain方法中,可以获得一个Instrumentation对象,我们可以向其中加入一个ClassFileTransformer对象,Transformer对象的实现如下所示:

2、AopAgentTransformer

  1. public class AopAgentTransformer implements ClassFileTransformer{
  2. public byte[] transform(ClassLoader loader, String className,
  3. Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
  4. byte[] classfileBuffer) throws IllegalClassFormatException {
  5. System.out.println("Transforming类" + className+"的字节码开始");
  6. /*
  7. TODO
  8. 修改字节码,并返回修改后的字节码
  9. */
  10. byte[] transformed = null;
  11. try {
  12. ClassReader cr = new ClassReader(new java.io.ByteArrayInputStream(classfileBuffer));
  13. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
  14. //ClassAdapter ca = new ClassAdapter(cw);
  15. TimeCountAdpter ca = new TimeCountAdpter(cw);
  16. cr.accept(ca, ClassReader.EXPAND_FRAMES);
  17. transformed = cw.toByteArray();
  18. }catch (RuntimeException re){
  19. re.printStackTrace();
  20. }catch (IOException e) {
  21. System.err.println("can't transform "+ className+" "+e);
  22. e.printStackTrace();
  23. }
  24. return transformed;
  25. }
  26. }

JVM会调用transform方法,这样就可以在JVM加载字节码文件时,获取到字节码并进行修改。

3、ASM修改字节码TimeCountAdpter

  1. /**
  2. * 监控所有方法的执行时间
  3. * @author forever
  4. *
  5. */
  6. public class TimeCountAdpter extends ClassVisitor implements Opcodes {
  7. private String owner;
  8. private boolean isInterface;
  9. private String filedName = "UDASMCN";
  10. private int acc = Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL;
  11. private boolean isPresent = false;
  12. private String methodName;
  13. public TimeCountAdpter(ClassVisitor classVisitor) {
  14. super(ASM6, classVisitor);
  15. }
  16. @Override
  17. public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
  18. super.visit(version, access, name, signature, superName, interfaces);
  19. owner = name;
  20. isInterface = (access & ACC_INTERFACE) != 0;
  21. }
  22. @Override
  23. public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
  24. if (name.equals(filedName)) {
  25. isPresent = true;
  26. }
  27. return super.visitField(access, name, descriptor, signature, value);
  28. }
  29. @Override
  30. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
  31. String[] exceptions) {
  32. MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
  33. if (!isInterface && mv != null && !name.equals("<init>") && !name.equals("<clinit>")) {
  34. methodName = name;
  35. AddTimerMethodAdapter at = new AddTimerMethodAdapter(mv);
  36. at.aa = new AnalyzerAdapter(owner, access, name, descriptor, at);
  37. at.lvs = new LocalVariablesSorter(access, descriptor, at.aa);
  38. return at.lvs;
  39. }
  40. return mv;
  41. }
  42. public void visitEnd() {
  43. if (!isInterface) {
  44. FieldVisitor fv = cv.visitField(acc, filedName, "Ljava/lang/String;", null, owner);
  45. if (fv != null) {
  46. fv.visitEnd();
  47. }
  48. }
  49. cv.visitEnd();
  50. }
  51. class AddTimerMethodAdapter extends MethodVisitor {
  52. private int time;
  53. private int maxStack;
  54. public LocalVariablesSorter lvs;
  55. public AnalyzerAdapter aa;
  56. public AddTimerMethodAdapter(MethodVisitor methodVisitor) {
  57. super(ASM6, methodVisitor);
  58. }
  59. @Override
  60. public void visitCode() {
  61. mv.visitCode();
  62. mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
  63. time = lvs.newLocal(Type.LONG_TYPE);
  64. mv.visitVarInsn(LSTORE, time);
  65. maxStack = 4;
  66. }
  67. @Override
  68. public void visitInsn(int opcode) {
  69. if (((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) && !isPresent) {
  70. mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
  71. mv.visitVarInsn(LLOAD, time);
  72. mv.visitInsn(LSUB);
  73. mv.visitVarInsn(LSTORE, time);
  74. mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
  75. mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
  76. mv.visitInsn(DUP);
  77. mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
  78. mv.visitFieldInsn(GETSTATIC, owner, filedName, "Ljava/lang/String;");
  79. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
  80. "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
  81. mv.visitLdcInsn(" " + methodName + ":");
  82. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
  83. "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
  84. mv.visitVarInsn(LLOAD, time);
  85. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;",
  86. false);
  87. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
  88. mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
  89. maxStack = Math.max(aa.stack.size() + 4, maxStack);
  90. }
  91. mv.visitInsn(opcode);
  92. }
  93. @Override
  94. public void visitMaxs(int maxStack, int maxLocals) {
  95. super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);
  96. }
  97. }
  98. }

4、编译maven install

这里的项目主要参考的是我上一篇文章的agentTest:,但是因为pom.xml中加入了其他jar包,所以为了把外部的jar包也打包进去,pom.xml要改为如下:

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>com.suibibk</groupId>
  4. <artifactId>AgentTest</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <packaging>jar</packaging>
  7. <dependencies>
  8. <dependency>
  9. <groupId>org.ow2.asm</groupId>
  10. <artifactId>asm</artifactId>
  11. <version>6.2.1</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.ow2.asm</groupId>
  15. <artifactId>asm-util</artifactId>
  16. <version>6.2.1</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.ow2.asm</groupId>
  20. <artifactId>asm-commons</artifactId>
  21. <version>6.2.1</version>
  22. </dependency>
  23. </dependencies>
  24. <build>
  25. <plugins>
  26. <plugin>
  27. <groupId>org.apache.maven.plugins</groupId>
  28. <artifactId>maven-jar-plugin</artifactId>
  29. <version>3.1.2</version>
  30. <configuration>
  31. <archive>
  32. <manifest>
  33. <addClasspath>true</addClasspath>
  34. </manifest>
  35. <manifestEntries>
  36. <Premain-Class>
  37. com.suibibk.AgentTest
  38. </Premain-Class>
  39. <!-- <Agent-Class>
  40. com.suibibk.AgentTest
  41. </Agent-Class> -->
  42. </manifestEntries>
  43. </archive>
  44. </configuration>
  45. </plugin>
  46. <plugin>
  47. <groupId>org.apache.maven.plugins</groupId>
  48. <artifactId>maven-shade-plugin</artifactId>
  49. <version>1.4</version>
  50. <configuration>
  51. <createDependencyReducedPom>false</createDependencyReducedPom>
  52. </configuration>
  53. <executions>
  54. <execution>
  55. <!-- 执行package的phase -->
  56. <phase>package</phase>
  57. <!-- 为这个phase绑定goal -->
  58. <goals>
  59. <goal>shade</goal>
  60. </goals>
  61. <configuration>
  62. <!-- 过滤掉以下文件,不打包 :解决包重复引用导致的打包错误-->
  63. <filters>
  64. <filter>
  65. <artifact>*:*</artifact>
  66. <excludes>
  67. <exclude>META-INF/*.SF</exclude>
  68. <exclude>META-INF/*.DSA</exclude>
  69. <exclude>META-INF/*.RSA</exclude>
  70. </excludes>
  71. </filter>
  72. </filters>
  73. <transformers>
  74. <transformer
  75. implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  76. <resource>META-INF/spring.handlers</resource>
  77. </transformer>
  78. <transformer
  79. implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
  80. <resource>META-INF/spring.schemas</resource>
  81. </transformer>
  82. </transformers>
  83. </configuration>
  84. </execution>
  85. </executions>
  86. </plugin>
  87. </plugins>
  88. </build>
  89. </project>

5、测试方法

  1. public class Test2 {
  2. public static void main(String[] args) {
  3. //System.out.println("main2");
  4. test();
  5. test2();
  6. }
  7. public static void test() {
  8. System.out.println("我是Test2的test方法");
  9. }
  10. public static void test2() {
  11. System.out.println("我是Test2的test2方法");
  12. }
  13. }

需要加上VM参数:

  1. -javaagent:D:\Work\eclipse_workspace\AgentTest\target\AgentTest-0.0.1-SNAPSHOT.jar=haha

运行输出如下内容,单位为纳秒:

  1. Transformingsun/misc/URLClassPath$FileLoader$1的字节码开始
  2. sun/misc/URLClassPath$FileLoader$1 getCodeSourceURL:1900
  3. sun/misc/URLClassPath$FileLoader$1 getInputStream:142300
  4. sun/misc/URLClassPath$FileLoader$1 getContentLength:124600
  5. Transformingcom/suibibk/test/Test2的字节码开始
  6. 我是Test2test方法
  7. com/suibibk/test/Test2 test:83100
  8. 我是Test2test2方法
  9. com/suibibk/test/Test2 test2:44600
  10. com/suibibk/test/Test2 main:314500

完成!

参考:
JavaAgent:在主程序运行之前和之后的代理程序
史上最通俗易懂的ASM教程
基于javaAgent和ASM字节码技术跟踪java程序调用链
JVM插码之五:Java agent+ASM实战—监控所有方法执行时间

 870

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2