0

Consider the following bytebuddy program to intercept method call load():

public class ByteJavaBuddyTest {
    public static class MemoryDatabase {
        public List<String> load(String info) {
            return Arrays.asList(info + ": foo", info + ": bar");
        }
    }

    public static class LoggerInterceptor {
        public static List<String> log(@SuperCall Callable<List<String>> zuper) throws Exception {
            System.out.println("Calling database");
            try {
                return zuper.call();
            } finally {
                System.out.println("Returned from database");
            }
        }
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        MemoryDatabase loggingDatabase = new ByteBuddy()
                .subclass(MemoryDatabase.class)
                .method(named("load")).intercept(MethodDelegation.to(LoggerInterceptor.class))
                .make()
                .load(MemoryDatabase.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded()
                .getDeclaredConstructor()
                .newInstance();
        System.out.println(loggingDatabase.load("qux"));
    }
}

It runs and prints:

Calling database
Returned from database
[qux: foo, qux: bar]

After conversion to Kotlin it doesn't run the interceptor, nor does it throw any exceptions.

object ByteBuddyKotlinTest {
    open class MemoryDatabase {
        fun load(info: String): List<String> {
            return Arrays.asList("$info: foo", "$info: bar")
        }
    }

    object LoggerInterceptor {
        @Throws(Exception::class)
        fun log(@SuperCall zuper: Callable<List<String>>): List<String> {
            println("Calling database")
            try {
                return zuper.call()
            } finally {
                println("Returned from database")
            }
        }
    }

    @Throws(
        NoSuchMethodException::class,
        IllegalAccessException::class,
        InvocationTargetException::class,
        InstantiationException::class
    )
    @JvmStatic
    fun main(args: Array<String>) {
        val loggingDatabase = ByteBuddy()
            .subclass(MemoryDatabase::class.java)
            .method(ElementMatchers.named("load")).intercept(MethodDelegation.to(LoggerInterceptor::class.java))
            .make()
            .load(MemoryDatabase::class.java.classLoader, ClassLoadingStrategy.Default.WRAPPER)
            .loaded
            .getDeclaredConstructor()
            .newInstance()
        println(loggingDatabase.load("qux"))
    }
}

Prints:

[qux: foo, qux: bar]

Since it doesn't throw any errors I don't really know where to start digging.

Kshitiz Sharma
  • 17,947
  • 26
  • 98
  • 169

1 Answers1

4

Byte Buddy proxies methods by overriding them. If a method is not declared open in Kotlin, it is final in Java byte code. If you open your method, your logic should work again.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Adding load open throws error: `Exception in thread "main" java.lang.IllegalArgumentException: None of [] allows for delegation from public java.util.List liquibase.ext.ksharma.change.ByteBuddyKotlinTest$MemoryDatabase.load(java.lang.String)` – Kshitiz Sharma Jan 07 '19 at 10:12
  • I assume that your interceptor method is not compiled to be public and static? What methods does LoggerInterceptor::class.java declare? You can check the class file using javap. – Rafael Winterhalter Jan 07 '19 at 10:48
  • 1
    Ah, I thought putting a method in an `object` made it static. However, you need to put `@JvmStatic` annotation on the method for that. Works now. Thank you for your help and for the awesome library! – Kshitiz Sharma Jan 07 '19 at 14:06
  • Do you think perhaps it should throw an error or log a warning instead of failing silently? – Kshitiz Sharma Jan 07 '19 at 15:37
  • it did throw an exceprion? – Rafael Winterhalter Jan 07 '19 at 21:16
  • It didn't in the first case (when method was not declared open). – Kshitiz Sharma Jan 08 '19 at 11:49
  • all classes declare final methods by inheriting them from object. it is hard to guess your intention but i understand that this would be useful many times. – Rafael Winterhalter Jan 08 '19 at 18:07