应用建议的Bytebuddy代理中的“重复类”错误

时间:2018-07-30 12:37:27

标签: java javaagents byte-buddy

我有一些基于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参数引用,所以过早加载了它,但这不是让代理重新定义它们的全部目的吗?

此外,在按需案例中它如何工作?我想我需要调整代理生成器,还是错过了一步?

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

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

好的,解决方案是使用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());
}

没有什么可以通过间接层解决的,但是我想认为它已经以某种更好的方式进行了处理。