I am attempting to migrate an AspectJ project to a Byte Buddy plugin and having some difficulty. I want to do compile-time byte code modifications.
The exception I am getting is :
[ERROR] Failed to execute goal net.bytebuddy:byte-buddy-maven-plugin:1.11.0:transform (default) on project timing-example: Failed to transform class files in /tmp/timing-example/target/classes: protected void com.walterjwhite.examples.timing.TimingExampleCommandLineHandler.doRun(java.lang.String[]) does not define an index 1 -> [Help 1]
Plugin: NOTE:
package com.walterjwhite.timing.plugin;
import static net.bytebuddy.matcher.ElementMatchers.*;
import com.walterjwhite.timing.annotation.Timing;
import java.io.IOException;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatchers;
@HashCodeAndEqualsPlugin.Enhance
public class TimingPlugin extends Plugin.ForElementMatcher implements Plugin.Factory {
public TimeoutPlugin() {
super(declaresMethod(isAnnotatedWith(Timing.class)));
}
@Override
public DynamicType.Builder<?> apply(
DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassFileLocator classFileLocator) {
System.out.println("Timing: start");
for (MethodDescription.InDefinedShape methodDescription :
typeDescription
.getDeclaredMethods()
.filter(
not(isBridge()).<MethodDescription>and(isAnnotatedWith(Timing.class)))) {
System.out.println("Timing: " + methodDescription);
if (methodDescription.isAbstract()) {
throw new IllegalStateException(
"Cannot implement timing on an abstract method: " + methodDescription);
}
builder = builder.visit(Advice.to(TimingAdvice.class).on(is(methodDescription)));
}
System.out.println("Timing: end");
return builder;
}
Advice: NOTE: I would like to wrap the original method invocation in a try-catch-finally so I can time it. I'm not sure I can do that with advice. In any case, that is further down the road, I want to see that I can write a plugin and have my code instrumented.
package com.walterjwhite.timing.plugin;
import java.lang.reflect.Method;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
public class TimingAdvice {
@RuntimeType
@Advice.OnMethodEnter
public static void onEnter(@Advice.This Object intercepted, @Origin Method method, @RuntimeType @AllArguments Object[] arguments)
throws Throwable {
System.out.println(System.currentTimeNanos());
}
}
Method being advised:
@Timing
@Override
protected void doRun(String... arguments) {
int i = 0;
while (true) {
try {
System.out.println("i:" + i++);
Thread.sleep(50);
} catch (InterruptedException e) {
System.out.println("Exiting as instructed to do so.");
System.exit(1);
}
}
}
excerpt from pom.xml:
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>1.11.0</version>
<executions>
<execution>
<goals>
<goal>transform</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.walterjwhite.aspects.timing</groupId>
<artifactId>plugin</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
</plugin>
Lastly, the plugin project has the file in place because the byte buddy plugin does pick it up. But, whenever it attempts to transform the class files, it fails. So, my configuration isn't quite right.
EDIT #2:
The pom is partially correct, the other issue I ran into was: NoClassDefFoundError
This was due to the fact that I needed the dependency also listed as a dependency for the project and not just the plugin. Ie:
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>1.11.0</version>
<executions>
<execution>
<goals>
<goal>transform</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.walterjwhite.aspects.timing</groupId>
<artifactId>plugin</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
</plugin>
</plugins
</build>
<dependencies>
<dependency>
<groupId>com.walterjwhite.aspects.timing</groupId>
<artifactId>plugin</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
...
You are blending annotations from the MethodDelegation
API with the Advice
API. The annotations are very similar as they intend to support the same approach but an unfortunate side effect is that they get confused. Instead of importing
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
you need to use annotations declared within the Advice
class with the same name. Ideally, you prefix all annotations with Advice
:
@Advice.OnMethodEnter
public static void onEnter(@Advice.This Object intercepted, @Advice.Origin Method method, @Advice.AllArguments Object[] arguments)
throws Throwable {
System.out.println(System.currentTimeNanos());
}
Note that the @RuntimeType
has no equivalent in the Advice
API. It is not normally needed.