"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()
,但这会在 premain
和 visit
之间造成丑陋的重复。
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 加载程序星座也更健壮。这样你就不需要重复你的逻辑。
我有一些基于 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()
,但这会在 premain
和 visit
之间造成丑陋的重复。
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 加载程序星座也更健壮。这样你就不需要重复你的逻辑。