"duplicate class" Bytebuddy 代理应用建议时出错

"duplicate class" error in a Bytebuddy agent applying Advice

我有一些基于 ByteBuddy 的工具,我想将其提供给嵌入式使用和代理。

代码是这样的:

public static void premain(String arguments, Instrumentation instrumentation) {
    installedInPremain = true;
    new AgentBuilder.Default()
            .type(ElementMatchers.named("com.acme.FooBar"))
            .transform((builder, typeDescription, classLoader, module) -> visit(builder))
            .installOn(instrumentation);

}

public static void instrumentOnDemand() {
    ByteBuddyAgent.install();
    DynamicType.Builder<URLPropertyLoader> typeBuilder = new ByteBuddy().redefine(FooBar.class);
    DynamicType.Builder<FooBar> visited = visit(typeBuilder);
    visited.make().load(FooBar.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}

private static <T> DynamicType.Builder<T> visit(DynamicType.Builder<T> builder) {
    return builder.visit(Advice.to(SnoopLoad.class).on(named("load").and(takesArguments(0))))
                .visit(Advice.to(SnoopOpenStream.class).on(named("openStream")))
                .visit(Advice.to(SnoopPut.class).on(named("put")));
}

然后在其他地方,有人会这样做:

new FooBar().load("abc123")

如果我是 运行 instrumentOnDemand,一切都会很好,但如果我是 运行 premain 代理,我会得到:

Exception in thread "main" java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "com/acme/FooBar"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.acme.FooBarAnalyzer.load(FooBarAnalyzer.java:121)
    at com.acme.FooBarAnalyzer.main(FooBarAnalyzer.java:107)

我猜是因为我的建议将 FooBar 引用为 @Advice.This 参数,所以它会过早加载,但是让代理能够重新定义这些不是全部意义所在吗?

另外,为什么在点播的情况下会起作用?我想我需要调整代理生成器,还是我错过了一步?

啊...在不理解的情况下复制教程的危险...也非常欢迎指向文档!

好的,解决方案是使用 AgentBuilder.Transformer.ForAdvice(),但这会在 premainvisit 之间造成丑陋的重复。

public static void premain(String arguments, Instrumentation instrumentation) {
    installedInPremain = true;
    new AgentBuilder.Default()
            .type(named("com.acme.FooBar"))
//          .transform((builder, typeDescription, classLoader, module) -> visit(builder))

             // CAN'T WE HAVE THIS SHARED???
            .transform(advice("load",           "SnoopLoad"))
            .transform(advice("openStream",     "SnoopOpenStream"))
            .transform(advice("put",            "SnoopPut"))
            .installOn(instrumentation);

}

private static AgentBuilder.Transformer.ForAdvice advice(String name, String snoopLoad) {
    return new AgentBuilder.Transformer.ForAdvice().advice(named(load), "com.acme.PropAnalyzerAgent$" + snoopLoad);
}

private static <T> DynamicType.Builder<T> visit(DynamicType.Builder<T> builder) {
    // CAN'T WE HAVE THIS SHARED???
    return builder.visit(Advice.to(SnoopLoad.class).on(named("load")))
                .visit(Advice.to(SnoopOpenStream.class).on(named("openStream")))
                .visit(Advice.to(SnoopPut.class).on(named("put")));
}


public static void instrumentOnDemand() {
    DynamicType.Builder<FooBar> typeBuilder = new ByteBuddy().redefine(FooBar.class);
    DynamicType.Builder<FooBar> visited = visit(typeBuilder);
    visited.make().load(FooBar.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}

没有什么是间接层无法解决的,但我认为它已经以某种更好的方式处理了。

您可以只将 AgentBuilder.Transformer.ForAdvice 用于启动和动态检测。这通常是一个更好的选择,因为它对不同的 class 加载程序星座也更健壮。这样你就不需要重复你的逻辑。