我有一些基于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 ...在不了解的情况下复制教程的危险...指向文档的指针也非常受欢迎!
答案 0 :(得分:1)
您可以仅将AgentBuilder.Transformer.ForAdvice
用于启动和动态检测。通常,这是一个更好的选择,因为它对于不同的类加载器星座也更健壮。这样,您无需重复逻辑。
答案 1 :(得分:0)
好的,解决方案是使用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());
}
没有什么可以通过间接层解决的,但是我想认为它已经以某种更好的方式进行了处理。