java instrumentation attach模式快速入門

本文旨在簡(jiǎn)單粗暴體驗(yàn)instrumentation attach模式的玩法,給讀者一個(gè)直觀的體驗(yàn),概念方面不多介紹

場(chǎng)景

有一個(gè)spring的http接口定義如下,每次調(diào)用返回一個(gè)隨機(jī)uuid,此處的RandomUtil采用的hutool的工具類。

@GetMapping("/play")
@ResponseBody
public String health() {
  return RandomUtil.simpleUUID();
}

期望通過(guò)編寫一個(gè)agent,attach到當(dāng)前進(jìn)程實(shí)現(xiàn)串改程序邏輯,每次調(diào)用都返回hello

開發(fā)一個(gè)agent jar

入口函數(shù)

先要寫一個(gè)agentmain函數(shù),類似我們寫helloword的main函數(shù)

public static void agentmain(String agentArgs, Instrumentation inst)  throws ClassNotFoundException, UnmodifiableClassException, InterruptedException {
  //注冊(cè)字節(jié)碼轉(zhuǎn)換邏輯
  inst.addTransformer(new PlayClassFileTransformer(), true);
  //使之生效
  inst.retransformClasses(RandomUtil.class);
  System.out.println("Agent Main Done");
}

字節(jié)碼轉(zhuǎn)換邏輯

函數(shù)內(nèi)部注冊(cè)了PlayClassFileTransformer, 內(nèi)部實(shí)現(xiàn)邏輯:

  • 如果不是RandomUtil,則返回null,表示不作替換
  • 否則替換新的字節(jié)碼內(nèi)容,字節(jié)碼內(nèi)容來(lái)自本地提前準(zhǔn)備好的一個(gè)class文件
public class PlayClassFileTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
        ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (!"cn/hutool/core/util/RandomUtil".equals(className)){
            return null;
        }

        return getBytesFromFile("/Users/***/code/***/instrument-play/docs/RandomUtil.class");
    }

    public static byte[] getBytesFromFile(String fileName) {
        try {
            // precondition
            File file = new File(fileName);
            InputStream is = new FileInputStream(file);
            long length = file.length();
            byte[] bytes = new byte[(int) length];

            // Read in the bytes
            int offset = 0;
            int numRead = 0;
            while (offset <bytes.length
                && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
                offset += numRead;
            }

            if (offset < bytes.length) {
                throw new IOException("Could not completely read file "
                    + file.getName());
            }
            is.close();
            return bytes;
        } catch (Exception e) {
            System.out.println("error occurs in _ClassTransformer!"
                + e.getClass().getName());
            return null;
        }
    }
}

注意: 此處字節(jié)碼是我提前準(zhǔn)備好的,基于hutool的源碼隨便改了一筆,把simpleUUID函數(shù)的返回值改為了hello。

retransformClasses

注冊(cè)完轉(zhuǎn)換器后,替換邏輯執(zhí)行的時(shí)機(jī)需要依賴于此,所以需要手動(dòng)執(zhí)行這個(gè)函數(shù),否則替換邏輯是不生效的。以下引用一小段ClassFileTransformer的注釋:

the transformer will be called for every new class definition and every class redefinition.

另外,此處因?yàn)樾枰付ㄐ枰猺etransform的類型,所以agent的工程里也引入了對(duì)hutool的依賴:

<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>4.1.3</version>
  <scope>provided</scope>
</dependency>

manifest文件

jar包生成后需要manifest文件,所以添加如下maven配置:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-assembly-plugin</artifactId>
  <configuration>
    <descriptorRefs>
      <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
    <archive>
      <manifestEntries>
        <Agent-Class>org.example.instrument.AgentMain</Agent-Class>
        <Can-Redefine-Classes>true</Can-Redefine-Classes>
        <Can-Retransform-Classes>true</Can-Retransform-Classes>
      </manifestEntries>
    </archive>
  </configuration>

  <executions>
    <execution>
      <goals>
        <goal>attached</goal>
      </goals>
      <phase>package</phase>
    </execution>
  </executions>
</plugin>

打包

mvn clean package

ATTACH

接下來(lái)需要把a(bǔ)gent jar attach到目標(biāo)進(jìn)程上去。

此處我們假設(shè)目標(biāo)進(jìn)程,已啟動(dòng),進(jìn)程號(hào)為8888,則attach的代碼如下:

public static void main(String[] args) throws InterruptedException, IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
    VirtualMachine vmObj = null;
    try {
        vmObj = VirtualMachine.attach("8888");
        if (vmObj != null) {
            vmObj.loadAgent("<jar path>/instrument-play-1.0-SNAPSHOT-jar-with-dependencies.jar", null);
        }
    } finally {
        if (null != vmObj) {
            vmObj.detach();
        }
    }
}

效果體驗(yàn)

完整源碼

查看

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容