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

原文地址:https://www.cnblogs.com/xuxiaojian/p/14537375.html