Apache SkyWalking Java Agent 07-Byte Buddy 操作字节码

基于 SkyWalking Java Agent 8.8.0 版本

通过之前文章的学习我们了解到 SkyWalking Java Agent 插件加载机制和插件体系。

本篇文章我们继续分析 SkyWalking Java Agent 源码,先回到插件加载部分的代码。

pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());

通过 PluginBootstrap#loadPlugins 我们完成了插件定义类的加载和实例化,然后将插件对象列表传入PluginFinder的构造方法,根据增强类的匹配规则对插件匹配做分类。

/**
 * The <code>PluginFinder</code> represents a finder , which assist to find the one from the given {@link
 * AbstractClassEnhancePluginDefine} list.
 */
public class PluginFinder {
    private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
    private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
    private final List<AbstractClassEnhancePluginDefine> bootstrapClassMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();

    public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) {
        // 对插件匹配分类
        for (AbstractClassEnhancePluginDefine plugin : plugins) {
            ClassMatch match = plugin.enhanceClass();

            if (match == null) {
                continue;
            }

            if (match instanceof NameMatch) {
                // 类名匹配
                NameMatch nameMatch = (NameMatch) match;
                // 根据目标类(比如 tomcat 中的 org.apache.catalina.core.StandardHostValve 类 )找到对应的增强插件定义类
                LinkedList<AbstractClassEnhancePluginDefine> pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
                if (pluginDefines == null) {
                    pluginDefines = new LinkedList<AbstractClassEnhancePluginDefine>();
                    nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
                }
                pluginDefines.add(plugin);
            } else {
                // 间接匹配
                signatureMatchDefine.add(plugin);
            }

            if (plugin.isBootstrapInstrumentation()) {
                // JDK 内置类
                bootstrapClassMatchDefine.add(plugin);
            }
        }
    }

    // 省略部分代码
    
}

主要是两种分类:

  • nameMatchDefine 类名匹配;
  • signatureMatchDefine 间接匹配;

如果是对JDK内置类的增强,则放入 bootstrapClassMatchDefine 中。

Byte Buddy

接下来我们进行到 Byte Buddy 部分的代码分析,Byte Buddy 是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改类,而无需编译器的帮助。

Byte Buddy is a code generation and manipulation library for creating and modifying Java classes during the runtime of a Java application and without the help of a compiler. Other than the code generation utilities that ship with the Java Class Library, Byte Buddy allows the creation of arbitrary classes and is not limited to implementing interfaces for the creation of runtime proxies. Furthermore, Byte Buddy offers a convenient API for changing classes either manually, using a Java agent or during a build.

更多内容可以访问 Byte Buddy 官网 https://bytebuddy.net/ 查看。

我们这里重点看下 SkyWalking 中是如何通过 Byte Buddy 实现插桩的核心代码

/**
 * The main entrance of sky-walking agent, based on javaagent mechanism.
 */
public class SkyWalkingAgent {
    private static ILog LOGGER = LogManager.getLogger(SkyWalkingAgent.class);

    /**
     * Main entrance. Use byte-buddy transform to enhance all classes, which define in plugins.
     */
    public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
        final PluginFinder pluginFinder;
        try {
            SnifferConfigInitializer.initializeCoreConfig(agentArgs);
        } catch (Exception e) {
            // try to resolve a new logger, and use the new logger to write the error log here
            // 配置初始化过程可能会抛出异常(验证非空参数),这里为了使用新的 LogResolver 需要重新获取日志对象
            LogManager.getLogger(SkyWalkingAgent.class)
                    .error(e, "SkyWalking agent initialized failure. Shutting down.");
            return;
        } finally {
            // refresh logger again after initialization finishes
            LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
        }

        try {
            pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
        } catch (AgentPackageNotFoundException ape) {
            LOGGER.error(ape, "Locate agent.jar failure. Shutting down.");
            return;
        } catch (Exception e) {
            LOGGER.error(e, "SkyWalking agent initialized failure. Shutting down.");
            return;
        }

        final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));

        AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
                nameStartsWith("net.bytebuddy.")
                        .or(nameStartsWith("org.slf4j."))
                        .or(nameStartsWith("org.groovy."))
                        .or(nameContains("javassist"))
                        .or(nameContains(".asm."))
                        .or(nameContains(".reflectasm."))
                        .or(nameStartsWith("sun.reflect"))
                        .or(allSkyWalkingAgentExcludeToolkit())
                        .or(ElementMatchers.isSynthetic()));

        // 省略部分代码

        agentBuilder.type(pluginFinder.buildMatch())
                    .transform(new Transformer(pluginFinder))
                    .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                    .with(new RedefinitionListener())
                    .with(new Listener())
                    .installOn(instrumentation);

        // 省略部分代码
    }

    // 省略部分代码
}

Byte Buddy 有两个地方的代码我们需要重点关注:

  • agentBuilder.type(pluginFinder.buildMatch()) 用于告诉 Byte Buddy 对哪些类进行增强,就是我们插件定义类中的AbstractClassEnhancePluginDefine#enhanceClass 方法声明的目标类的匹配规则;
  • transform(new Transformer(pluginFinder)) 用于匹配到的目标类进行增强,调用了插件定义类中的 AbstractClassEnhancePluginDefine#define 方法。

我们看下 PluginFinder#buildMatch() 方法的实现

/**
 * The <code>PluginFinder</code> represents a finder , which assist to find the one from the given {@link
 * AbstractClassEnhancePluginDefine} list.
 */
public class PluginFinder {
    private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
    private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
    private final List<AbstractClassEnhancePluginDefine> bootstrapClassMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();

    // 省略部分代码

    public ElementMatcher<? super TypeDescription> buildMatch() {
        ElementMatcher.Junction judge = new AbstractJunction<NamedElement>() {
            @Override
            public boolean matches(NamedElement target) {
                // 根据类名匹配
                return nameMatchDefine.containsKey(target.getActualName());
            }
        };
        judge = judge.and(not(isInterface()));
        for (AbstractClassEnhancePluginDefine define : signatureMatchDefine) {
            ClassMatch match = define.enhanceClass();
            if (match instanceof IndirectMatch) {
                // 间接匹配(比如 PrefixMatch、MethodAnnotationMatch、RegexMatch 等)
                judge = judge.or(((IndirectMatch) match).buildJunction());
            }
        }
        return new ProtectiveShieldMatcher(judge);
    }

    public List<AbstractClassEnhancePluginDefine> getBootstrapClassMatchDefine() {
        return bootstrapClassMatchDefine;
    }
}

其中 target.getActualName() 是 Byte Buddy 的 API,用于获取正在加载的类的全类名。

SkyWalking 通过自定义 Transformer 对目标类增强,PluginFinder 类里面包含我们之前实例化的插件定义类。

private static class Transformer implements AgentBuilder.Transformer {
    private PluginFinder pluginFinder;

    Transformer(PluginFinder pluginFinder) {
        this.pluginFinder = pluginFinder;
    }

    @Override
    public DynamicType.Builder<?> transform(final DynamicType.Builder<?> builder,
                                            final TypeDescription typeDescription,
                                            final ClassLoader classLoader,
                                            final JavaModule module) {
        LoadedLibraryCollector.registerURLClassLoader(classLoader);
        // 根据目标类(比如 tomcat 中的 org.apache.catalina.core.StandardHostValve 类 )找到对应的增强插件定义类
        List<AbstractClassEnhancePluginDefine> pluginDefines = pluginFinder.find(typeDescription);
        if (pluginDefines.size() > 0) {
            DynamicType.Builder<?> newBuilder = builder;
            EnhanceContext context = new EnhanceContext();
            for (AbstractClassEnhancePluginDefine define : pluginDefines) {
                // 调用增强插件的 define 方法
                DynamicType.Builder<?> possibleNewBuilder = define.define(
                        typeDescription, newBuilder, classLoader, context);
                if (possibleNewBuilder != null) {
                    newBuilder = possibleNewBuilder;
                }
            }
            if (context.isEnhanced()) {
                LOGGER.debug("Finish the prepare stage for {}.", typeDescription.getName());
            }

            return newBuilder;
        }

        LOGGER.debug("Matched class {}, but ignore by finding mechanism.", typeDescription.getTypeName());
        return builder;
    }
}

首先通过 PluginFinder#find 方法查找目标类对应的插件定义类,我们先看下是如何查找的

/**
 * The <code>PluginFinder</code> represents a finder , which assist to find the one from the given {@link
 * AbstractClassEnhancePluginDefine} list.
 */
public class PluginFinder {
    private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
    private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
    private final List<AbstractClassEnhancePluginDefine> bootstrapClassMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();

    // 省略部分代码

    public List<AbstractClassEnhancePluginDefine> find(TypeDescription typeDescription) {
        List<AbstractClassEnhancePluginDefine> matchedPlugins = new LinkedList<AbstractClassEnhancePluginDefine>();
        // 获取类的全类名
        String typeName = typeDescription.getTypeName();
        if (nameMatchDefine.containsKey(typeName)) {
            matchedPlugins.addAll(nameMatchDefine.get(typeName));
        }

        for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {
            IndirectMatch match = (IndirectMatch) pluginDefine.enhanceClass();
            if (match.isMatch(typeDescription)) {
                matchedPlugins.add(pluginDefine);
            }
        }

        return matchedPlugins;
    }

    // 省略部分代码
    
}

查找过程就是从不同的插件分类中去匹配,其中 TypeDescription.getTypeName() 通过 Byte Buddy 的 API 获取类的全类名。

获取到目标类对应的插件定义类之后,就是调用插件定义类中的 AbstractClassEnhancePluginDefine#define 方法对类进行增强,这里就是前面文章Apache SkyWalking Java Agent 06-插件定义体系 中最后留下的问题答案。

通过以上分析我们可以看到之前对插件定义类的所有的工作(加载、实例化等)都是为这里的 Byte Buddy 服务的,告诉 Byte Buddy 对哪些类进行增强。

那么AbstractClassEnhancePluginDefine#define 是如何对目标类进行增强呢,我将在下一篇进行介绍,敬请关注。

更多精彩内容请关注公众号 geekymv,喜欢请分享给更多的朋友哦」如有问题,欢迎交流。

java.lang.UnsupportedOperationException: class redefinition failed: attempted to change superclass or interfaces at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method) at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.skywalking.apm.dependencies.net.bytebuddy.utility.Invoker$Dispatcher.invoke(Unknown Source) at org.apache.skywalking.apm.dependencies.net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForNonStaticMethod.invoke(JavaDispatcher.java:1013) at org.apache.skywalking.apm.dependencies.net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler.invoke(JavaDispatcher.java:1142) at org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.$Proxy17.retransformClasses(Unknown Source) at org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy$Collector$ForRetransformation.doApply(AgentBuilder.java:7982) at org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy$Collector.apply(AgentBuilder.java:7797) at org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy.apply(AgentBuilder.java:5619) at org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$Default.doInstall(AgentBuilder.java:10400) at org.apache.skywalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$Default.installOn(AgentBuilder.java:10308) at org.apache.skywalking.apm.agent.SkyWalkingAgent.premain(SkyWalkingAgent.java:148)
最新发布
04-23
<think>好的,我现在需要解决用户提到的Java运行时出现的UnsupportedOperationException异常,尤其是与SkyWalkingByteBuddy相关的类重定义失败问题。首先,我得理清楚这个问题的背景和可能的原因。 用户提到在使用SkyWalkingByteBuddy时,出现了类重定义失败的错误,错误信息包括“attempted to change superclass or interfaces”以及“attempted to add a method”。根据引用中的信息,这些错误通常与JVM的Instrumentation机制有关,特别是在使用retransformClasses方法时遇到的限制。比如,引用[1]和[2]指出修改类的超类或接口,或者添加方法会导致异常,而引用[3]和[4]则提到retransformClasses的限制以及类重新转换的过程。 接下来,我需要分析可能的原因。首先,JVM的Instrumentation API在重新转换类时有严格的限制,不允许改变类的结构,如修改继承关系或添加/删除方法。SkyWalking作为APM工具,可能通过ByteBuddy动态修改字节码来实现监控,如果在这个过程中尝试了这些不被允许的修改,就会触发异常。另外,多个Agent(如SkyWalking和其他工具)同时修改同一个类,可能导致冲突,引发问题。 然后,我需要考虑解决方案。可能的步骤包括:检查Agent冲突,确保使用兼容版本的SkyWalkingByteBuddy,调整字节码增强策略避免结构修改,配置排除不需要增强的类,以及调整JVM启动参数。例如,用户可能需要检查是否有其他Agent同时运行,并尝试排除冲突的类;或者确保使用的SkyWalking版本与项目依赖的ByteBuddy版本兼容,避免因版本不匹配导致的问题。此外,使用SkyWalking的配置选项来限制或排除某些类的增强,可能减少冲突的发生。 还要注意,JVM的HotSwap机制在调试时允许有限的重定义,但生产环境中不可依赖。因此,用户可能需要通过配置或代码调整来规避这些限制。比如,使用@IgnoreHierarchy注解忽略特定类层次结构,或在agent.config中设置class_exclude_matchers来排除特定类。 最后,验证解决方案的有效性可能需要用户逐步应用每个建议,观察异常是否消失,同时监控应用的行为是否符合预期。如果问题依旧,可能需要更深入的日志分析或联系SkyWalking社区寻求帮助。</think>针对Java运行时出现的$java.lang.UnsupportedOperationException$类重定义失败错误,特别是与SkyWalkingByteBuddy相关的场景,以下是系统性解决方案: ### 一、问题根源分析 该异常直接源于JVM的类重定义限制: 1. **结构修改限制**:通过$Instrumentation.retransformClasses()$重定义类时,禁止修改类的超类、接口或方法签名[^3][^4] 2. **字节码冲突**:当SkyWalkingAgent与其他Agent(如混沌工程工具)同时操作同一类时,可能触发字节码修改冲突[^1] 3. **ByteBuddy兼容性**:SkyWalking 8.x+使用ByteBuddy作为字节码操作框架,版本不匹配会导致增强策略冲突 ### 二、解决方案分步实施 #### 1. Agent加载顺序调整 ```bash # 启动时显式指定agent加载顺序 java -javaagent:skywalking-agent.jar -javaagent:other-agent.jar ... ``` * 确保SkyWalking Agent作为首个加载项 * 通过$jcmd <PID> VM.command_line$验证加载顺序 #### 2. 类增强排除配置 在SkyWalking Agent的`agent.config`中添加: ```properties # 排除特定包路径 class_exclude_matchers=org.example.conflict.* # 禁用特定插件 plugin.mybatis.enhance=false ``` #### 3. ByteBuddy版本对齐 在项目pom.xml显式声明版本: ```xml <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.12.23</version> <!-- 匹配SkyWalking官方测试版本 --> </dependency> ``` 使用$mvn dependency:tree$验证依赖树 #### 4. 增强策略优化 ```java new AgentBuilder.Default() .ignore(named("com.alibaba.druid.pool.DruidDataSource")) .with(RedefinitionStrategy.RETRANSFORMATION) .with(InitializationStrategy.NoOp.INSTANCE) .disableClassFormatChanges(); // 禁止修改类结构 ``` ### 三、诊断工具使用 #### 1. 检查已加载类 ```bash jcmd <PID> VM.class_hierarchy -i <冲突类全限定名> ``` #### 2. 增强过程调试 在`agent.config`启用调试: ```properties logging.level.org.apache.skywalking=DEBUG ``` #### 3. JVM启动参数 ```bash -XX:+TraceClassLoading -XX:+TraceClassUnloading ``` ### 四、替代方案 当无法解决冲突时: 1. **使用Bootstrap Classloader**: ```java Instrumentation inst = ByteBuddyAgent.install(); new AgentBuilder.Default() .with(new AgentBuilder.InjectionStrategy.UsingBootstrapClassLoader()); ``` 2. **切换增强模式**: ```properties # 在agent.config中设置 agent.is_open_redirect_enhancement=true ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值