You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

两个Java Agent冲突导致Throwable类getMessage()方法增强丢失问题求助

两个Java Agent冲突导致Throwable类getMessage()方法增强丢失问题求助

问题描述

我需要对java.lang.Throwable的getMessage()方法做增强,输出自定义日志,自己写的FirstAgent单独运行完全正常。但当它和另一个厂商的Agent(我用SecondAgent模拟了这个场景)一起附加到JVM时,我的增强效果就丢失了。单独跑两个Agent都没问题,但一起跑的时候,先加载的FirstAgent对Throwable的修改会被后加载的SecondAgent覆盖——第二个Agent加载Throwable时用的是原始类的字节码,不是第一个Agent修改后的版本,然后它自己增强后就把之前的修改冲掉了。

我是不是哪里操作错了?求各位大佬给点建议!

复现代码

FirstAgent.java

import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.jar.JarFile;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

public class FirstAgent implements ClassFileTransformer {

    public static void premain(String agentArgs, Instrumentation inst) {
        try {
            String agentJarLocation = new File(
                FirstAgent.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
            inst.appendToBootstrapClassLoaderSearch(new JarFile(agentJarLocation));
        } catch (Exception e2) {
            e2.printStackTrace();
        }
        try {
            Class<?> targetClazz = Class.forName("java.lang.Throwable");
            FirstAgent fa = new FirstAgent();
            try {
                inst.addTransformer(fa, true);
                inst.retransformClasses(targetClazz);
            } catch (Exception ex) {
                throw new RuntimeException("Failed to transform [" + targetClazz.getName() + "]", ex);
            } finally {
                inst.removeTransformer(fa);
            }
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = classfileBuffer;
        try {
            ClassPool classPool = ClassPool.getDefault();
            CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod method : methods) {
                if (method.getName().contains("getMessage") && Modifier.isPublic(method.getModifiers())) {
                    method.insertBefore("System.out.println(\"First Custome Message\");");
                }
            }
            byteCode = ctClass.toBytecode();
            ctClass.detach();
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
        return byteCode;
    }
}

SecondAgent.java

import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.jar.JarFile;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

public class SecondAgent implements ClassFileTransformer {

    public static void premain(String agentArgs, Instrumentation inst) {
        try {
            String agentJarLocation = new File(
                SecondAgent.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
            inst.appendToBootstrapClassLoaderSearch(new JarFile(agentJarLocation));
        } catch (Exception e2) {
            e2.printStackTrace();
        }
        try {
            Class<?> targetClazz = Class.forName("java.lang.Throwable");
            SecondAgent sa = new SecondAgent();
            try {
                inst.addTransformer(sa, true);
                inst.retransformClasses(targetClazz);
            } catch (Exception ex) {
                throw new RuntimeException("Failed to transform [" + targetClazz.getName() + "]", ex);
            } finally {
                inst.removeTransformer(sa);
            }
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = classfileBuffer;
        try {
            ClassPool classPool = ClassPool.getDefault();
            CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod method : methods) {
                if (method.getName().contains("getMessage") && Modifier.isPublic(method.getModifiers())) {
                    method.insertBefore("System.out.println(\"Secocnd Custome Message\");");
                }
            }
            byteCode = ctClass.toBytecode();
            ctClass.detach();
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
        return byteCode;
    }
}

问题分析与解决方案

核心问题原因

你遇到的问题本质是Java Agent的类重转换机制和Transformer生命周期管理不当:

  1. Transformer被提前移除:每个Agent在做完retransformClasses后立刻调用removeTransformer,导致JVM中没有保留你的Transformer逻辑。后续Agent触发重转换时,无法基于你的修改继续处理,只能拿到原始类字节码重新增强,直接覆盖之前的修改。
  2. 重转换的字节码来源:当调用retransformClasses时,JVM会将类的原始字节码(从类加载器的原始来源读取)传入Transformer,而不是当前已加载的修改后的字节码——除非Transformer链中有之前的Transformer处理过这个类。

具体修复步骤

1. 保留Transformer,维持链式执行

Java Agent的Transformer是链式工作的:前一个Transformer输出的字节码会作为后一个Transformer的输入。所以不要在增强后立刻移除Transformer,让后续Agent能基于你的修改继续处理。

2. 优化类加载与重转换逻辑

避免主动调用Class.forName提前加载目标类(除非你确定它还没被加载),而是让Transformer在类加载/重转换时自动触发。如果类已经被加载,再触发重转换即可。

3. 修改Transformer的匹配逻辑

把method.getName().contains("getMessage")改成精确匹配"getMessage",避免误增强其他包含该字符串的方法(比如getLocalizedMessage)。

修改后的示例代码(FirstAgent)

import java.io.ByteArrayInputStream;
import java.io.File;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.jar.JarFile;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;

public class FirstAgent implements ClassFileTransformer {

    public static void premain(String agentArgs, Instrumentation inst) {
        try {
            // 将Agent Jar加入Bootstrap类加载器搜索路径,确保Javassist能处理核心类
            String agentJarLocation = new File(
                FirstAgent.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getPath();
            inst.appendToBootstrapClassLoaderSearch(new JarFile(agentJarLocation));
            
            // 添加Transformer,不移除,保持链式执行
            FirstAgent fa = new FirstAgent();
            inst.addTransformer(fa, true);
            
            // 如果Throwable已经被加载,触发重转换
            try {
                Class<?> targetClazz = Class.forName("java.lang.Throwable");
                inst.retransformClasses(targetClazz);
            } catch (Exception ex) {
                throw new RuntimeException("Failed to retransform Throwable", ex);
            }
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        // 只处理java.lang.Throwable类
        if (!"java/lang/Throwable".equals(className)) {
            return classfileBuffer;
        }

        byte[] byteCode = classfileBuffer;
        try {
            ClassPool classPool = ClassPool.getDefault();
            // 基于传入的当前字节码创建CtClass,确保是最新的修改版本
            CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod method : methods) {
                // 精确匹配方法名,避免误增强
                if ("getMessage".equals(method.getName()) && Modifier.isPublic(method.getModifiers())) {
                    method.insertBefore("System.out.println(\"First Custome Message\");");
                }
            }
            byteCode = ctClass.toBytecode();
            ctClass.detach();
        } catch (Throwable ex) {
            ex.printStackTrace();
        }
        return byteCode;
    }
}

SecondAgent也需要做完全相同的修改:移除finally块中的removeTransformer调用,精确匹配方法名。

额外注意事项

  • Agent加载顺序:JVM会按照Agent的启动顺序执行Transformer,如果你需要你的Agent优先增强,要确保它先被附加到JVM。
  • 核心类增强风险:增强java.lang包下的类要格外小心,避免引入副作用(比如死循环、破坏类结构),建议在测试环境充分验证。
  • Javassist版本兼容性:确保你使用的Javassist版本与目标JVM版本兼容,避免出现字节码格式不兼容的问题。

备注:内容来源于stack exchange,提问作者Sri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 11:58:16