• Java Attach API使用笔记


    手敲代码来体验IDEA+ASM+Java Attach API实现方法增强的一个示例过程记录。

    需求和目的

    /**
     * 模拟业务方法
     * @author xujian
     * 2021-03-12 10:52
     **/
    public class MyBizMain {
        public String foo() {
            return "------我是MyBizMain-----";
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyBizMain myBizMain = new MyBizMain();
            while (true) {
                System.out.println(myBizMain.foo());
                Thread.sleep(1000);
            }
        }
    }
    

    有一个程序MyBizMain.java,循环调用foo方法打印-“-----我是MyBizMain-----”,我们的目的是在其打印过程中,通过java agent将其打印的内容修改为“------我是MyBizMain的Agent-----”

    实现过程

    创建Attach程序

    1、在IDEA中新建一maven项目attach-demo;
    2、引入ASM相关依赖

    <dependency>
                <groupId>org.ow2.asm</groupId>
                <artifactId>asm</artifactId>
                <version>7.1</version>
            </dependency>
            <dependency>
                <artifactId>asm-commons</artifactId>
                <groupId>org.ow2.asm</groupId>
                <version>7.1</version>
            </dependency>
    

    3、引入tools.jar
    因为要使用到VirtualMachine,所以需要手动引入JDK目录下Contents/Home/lib/tools.jar,如下图所示:
    在这里插入图片描述

    4、编写attach代码

    /**
     * @author xujian
     * 2021-03-12 13:45
     **/
    public class MyAttachMain {
        public static void main(String[] args) {
            VirtualMachine vm = null;
            try {
                vm = VirtualMachine.attach("3188");//MyBizMain进程ID
                vm.loadAgent("/Users/jarry/IdeaProjects/agent-demo/target/agent-demo-1.0-SNAPSHOT.jar");//java agent jar包路径
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (vm != null) {
                    try {
                        vm.detach();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    创建Agent程序

    1、在IDEA中新建一个maven项目agent-demo
    2、引入ASM相关依赖以及打jar包的maven插件

    <dependency>
                <groupId>org.ow2.asm</groupId>
                <artifactId>asm</artifactId>
                <version>7.1</version>
            </dependency>
            <dependency>
                <artifactId>asm-commons</artifactId>
                <groupId>org.ow2.asm</groupId>
                <version>7.1</version>
            </dependency>
            <plugin>
            ...
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
              <archive>
                <!-- 打jar的文件清单,对应META-INF/MANIFEST.MF文件 -->
                <manifestEntries>
                  <!-- 主程序启动类 -->
                  <Agent-Class>
                    org.example.MyBizAgentMain
                  </Agent-Class>
                  <!-- 允许重新定义类 -->
                  <Can-Redefine-Classes>true</Can-Redefine-Classes>
                  <!-- 允许转换并重新加载类 -->
                  <Can-Retransform-Classes>true</Can-Retransform-Classes>
                </manifestEntries>
              </archive>
            </configuration>
          </plugin>
    

    3、自定义ASM ClassVisitor

    /**
     * 自定义ClassVisitor,修改foo方法字节码
     * @author xujian
     * 2021-03-12 11:14
     **/
    public class MyClassVisitor extends ClassVisitor {
        public MyClassVisitor(int api, ClassVisitor classVisitor) {
            super(api, classVisitor);
        }
    
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
                                         String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
            if ("foo".equals(name)) {
                System.out.println("----准备修改foo方法----");
                return new MyMethodVisitor(api,mv,access,name,descriptor);
            }
            return mv;
        }
    }
    

    4、自定义ASM MethodVisitor

    /**
     * 自定义MethodVisitor,修改字节码
     * @author xujian
     * 2021-03-12 11:02
     **/
    public class MyMethodVisitor extends AdviceAdapter {
    
    
        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }
    
        @Override
        protected void onMethodEnter() {
            mv.visitLdcInsn("------我是MyBizMain的Agent-----");//从常量池加载字符串
            mv.visitInsn(ARETURN);//返回
        }
    }
    

    5、自定义ClssFileTransformer

    /**
     * 自定义类文件转换器,通过ASM修改MyBizMain类字节码
     * @author xujian
     * 2021-03-12 11:42
     **/
    public class MyClassFileTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
            if (!"agent/MyBizMain".equals(className)) return classfileBuffer;
            //以下为ASM常规操作,详情可以查看ASM使用相关文档
            ClassReader cr = new ClassReader(classfileBuffer);
            ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES);
            ClassVisitor cv = new MyClassVisitor(ASM7,cw);
            cr.accept(cv,ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
            return cw.toByteArray();
        }
    }
    

    6、编写agent程序

    /**
     * @author xujian
     * 2021-03-12 10:58
     **/
    public class MyBizAgentMain {
        public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
            System.out.println("---agent called---");
            inst.addTransformer(new MyClassFileTransformer(),true);//添加类文件转换器,第二个参数必须设置为true,表示可以重新转换类文件
            Class[] classes = inst.getAllLoadedClasses();
            for (int i = 0; i < classes.length; i++) {
                if ("agent.MyBizMain".equals(classes[i].getName())) {
                    System.out.println("----重新加载MyBizMain开始----");
                    inst.retransformClasses(classes[i]);
                    System.out.println("----重新加载MyBizMain完毕----");
                    break;
                }
            }
        }
    }
    

    启动程序

    1. 先启动MyBizMain.java程序,使用jps命令查询其对应的进程号;
    2. 将上一步拿到的进程号填写到MyAttachMain.java的对应位置vm = VirtualMachine.attach("3188");//MyBizMain进程ID
    3. 使用maven打包插件将agent-demo项目打包成agent-demo.jar;
    4. 将上一步得到的jar包路径填写到MyAttachMain.java的对应位置vm.loadAgent("/Users/jarry/IdeaProjects/agent-demo/target/agent-demo-1.0-SNAPSHOT.jar");//java agent jar包路径
    5. 启动MyAttachMain.java程序查看输出结果;

    达到的效果

    在这里插入图片描述

    总结

    字节码层面的方法增强=修改字节码(ASM等字节码操作框架)+修改后的字节码重新加载(Java Agent、Java Attach API、Instrumentation)。


    详细的代码示例可以参考https://github.com/xujian01/blogcode/tree/master/src/main/java/javaagent

  • 相关阅读:
    [HNOI2015]实验比较 树形dp+组合数学
    【bzoj1090】 [SCOI2003]字符串折叠
    hdu4514(非连通图的环判断与图中最长链)(树的直径)
    数据类型进阶 续1
    数据类型进阶
    二进制补码
    基本数据类型的包装类
    变量的作用域
    用变量保存多种类型的数据
    用变量简化计算
  • 原文地址:https://www.cnblogs.com/xuxiaojian/p/14537375.html
Copyright © 2020-2023  润新知