3

I'm trying to use byte buddy to intercept methods but have found that it only appears to work when you sub-type an interface.

The code snippet below demonstrates this, you can see "hello" being passed into the same "message" method of the 3 types but only the Interface subtype is intercepted.

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.attribute.AnnotationRetention;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Morph;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

class Sandbox {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        testIt(StaticClass.class).message("hello");
        testIt(AbstractStaticClass.class).message("hello");
        testIt(Interface.class).message("hello");
    }

    private static <T> T testIt(Class<T> aClass) throws IllegalAccessException, InstantiationException {
        return new ByteBuddy().with(AnnotationRetention.ENABLED)
            .subclass(aClass)
            .method(
                    ElementMatchers.isAnnotatedWith(AnAnnotation.class)
            ).intercept(
                    MethodDelegation.withDefaultConfiguration()
                        .withBinders(Morph.Binder.install(Invoker.class))
                        .to(new Interceptor()))
            .make()
            .load(Sandbox.class.getClassLoader())
            .getLoaded()
            .newInstance();
    }

    public interface Invoker {
        Object invoke(Object[] args);
    }

    public static class Interceptor {
    @RuntimeType
    public Object intercept(@This Object proxy, @Origin Method method, @Morph(defaultMethod = true) Invoker invoker, @AllArguments Object[] args) {
            return invoker.invoke(new Object[]{"there"});
        }
    }

    public static class StaticClass {
        @AnAnnotation
        String message(String message) {
            System.out.println(getClass().getName() + ": message: " + message);
            return message;
        }
    }

    public static abstract class AbstractStaticClass {
        @AnAnnotation
        String message(String message) {
            System.out.println(getClass().getName() + ": message: " + message);
            return message;
        }
    }

    public interface Interface {
        @AnAnnotation
        default String message(String message) {
            System.out.println(getClass().getName() + ": message: " + message);
            return message;
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    private @interface AnAnnotation {}
}

I also found that if I changed the classes to inherit each other, like below, then the method doesn't execute at all for the AbstractStaticClass and StaticClass.

public static class StaticClass extends AbstractStaticClass { ... }
public static abstract class AbstractStaticClass implements Interface { ... }
public interface Interface { ... }

How can I intercept the methods of classes as well of interfaces?

Thank you

esQmo_
  • 1,464
  • 3
  • 18
  • 43
sbnarra
  • 556
  • 4
  • 9
  • 25

1 Answers1

2

You are defining package-private methods which are only visible within a package. You are also using the default class injection strategy which will create a new class loader to load your injected class. At runtime, two packages are only equal if they have the same name and class loader, similarly to classes.

Actually, Byte Buddy overrides the methods for you as the class creation does not consider the target class loader. You can verify this by calling getDeclaredMethod on your generated class and invoking them. However, Java will not virtually dispatch such package-foreign methods even if they have the same signature. To define classes in the same class loader, you can use ClassLoadingStrategy.Default.INJECTION. Do however note that this strategy is discouraged as it relies on unsafe API. In Java 9+ you can use ClassLoadingStrategy.UsingLookup instead what is safe.

You can of course also change your methods to be public or protected.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thanks Rafael, I also had to change the defaultMethod property on my @Morph annotation back to false for my classes to work. All is working now, great lib btw :) – sbnarra Aug 18 '18 at 00:32