I've got some ByteBuddy-based instrumentation that I want to provide both for embedded use and as an agent.
The code goes something like this:
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")));
}
Then somewhere else, somebody would do:
new FooBar().load("abc123")
And if I was running with instrumentOnDemand
, everything would be fine and dandy, but if I was running the agent with premain, I'd get:
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)
I would guess that because my advices reference FooBar
as @Advice.This
parameter, it gets loaded prematurely, but wasn't the whole point of having an agent to be able to redefine these?
Also, how come it worked in the on-demand case? I guess I need to tweak the agent builder, or am I missing a step?
Gah... the perils of copying tutorials without understanding... pointers to documentation are also more than welcome!
You can just use the AgentBuilder.Transformer.ForAdvice
for both the startup and the dynamic instrumentation. This is normally a better choice as this is also more robust against different class loader constellations. This way you do not need to duplicate your logic.