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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM