Search code examples
javabyte-buddy

Problem migrating from AspectJ to Byte Buddy Plugin


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>

...


Solution

  • 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.