Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intercepting calls to Java 8 lambda-expressions using Byte Buddy

I try to intercept calls to methods and calls to Java 8 lambda expressions using a Byte Buddy AgentBuilder as follows:

static {
  final Instrumentation inst = ByteBuddyAgent.install();
  new AgentBuilder.Default()
        .type(ElementMatchers.nameContainsIgnoreCase("foo"))
        .transform((builder, typeDescription) ->
                builder.method(ElementMatchers.any())
                        .intercept(MethodDelegation.to(LogInterceptor.class)))
        .installOn(inst);
}

public static class LogInterceptor {
  @RuntimeType
  public static Object log(@SuperCall Callable<?> superCall) throws Exception {
    System.out.println("yeah...");
    return superCall.call();
  }
}

I'm using Byte Buddy v0.7.1.

It can intercept the following Runnable (anonymous class):

FunnyFramework.callMeLater(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from inner class");
    }
});

and of course any calls to objects defined as normal (non-anonymous) classes. But the interception does not work for lambda expression's like:

FunnyFramework.callMeLater(() -> {
    System.out.println("Hello from lambda");
});

How can I intercept also the lambda expression calls? There's no such thing as a LambdaInterceptor in Byte Buddy, as far as I know.

like image 508
Peti Avatar asked Nov 25 '15 08:11

Peti


People also ask

Is lambda expressions introduced in Java 8?

Lambda expressions are a new and important feature included in Java SE 8. They provide a clear and concise way to represent one method interface using an expression. Lambda expressions also improve the Collection libraries making it easier to iterate through, filter, and extract data from a Collection .

How is lambda Expresssion represented by the JVM?

For Lambda expressions, the compiler doesn't translate them into something which is already understood by JVM. Lambda syntax that is written by the developer is desugared into JVM level instructions generated during compilation, which means the actual responsibility of constructing lambda is deferred to runtime.

Which functional interface is used for lambda expression?

Runnable is a functional interface, that's why we can use lambda expression to create it's instance. Since run() method takes no argument, our lambda expression also have no argument.

Why are lambdas expression used in Java 8?

In Java 8, using lambda expression and Stream API we can pass processing logic of elements into methods provided by collections and now collection is responsible for parallel processing of elements and not the client. Also, parallel processing effectively utilizes multicore CPUs used nowadays.


1 Answers

The Java virtual machine does not permit a transformation of a class files representing a lambda expression. Classes that represent lambda expressions are loaded by so-called anonymous class loaders (not to be confused with classical anonymous classes) that inherit another class's security context, e.g. a class that is loaded with an anonymous class loader which binds the loaded class to another class Foo can access private methods of Foo. This loading happens explicitly using the sun.misc.Unsafe API.

Byte Buddy hooks into the Java instrumentation API which allows the application of ClassFileTransformers to hook into a ClassLoaders loading process. As anonymous class loaders are not considered ClassLoaders in the common sense, the instrumentation API does not allow for such instrumentations and therefore prohibits the instrumentation of lambda expressions.

This is of course unfortunate for some use cases, but in most real-life applications, there is no real requirement for instrumenting lambda expression. Many real-world instrumentations are for example applied to methods that are annotated with a given annotation what is not possible to apply to lambda expressions or to classes that are more complex than a functional interface.


UPDATE: With Byte Buddy version 1.1.0, it is possible to instrument classes that represent lambda expressions. For this, Byte Buddy instruments the JVM's LambdaMetafactory and replaces the class generation with a custom definition. To activate this feature, execute the following step in the builder:

new AgentBuilder.Default()
  .with(LambdaInstrumentationStrategy.ENABLED)

Note that this does only work with OpenJDK 8u40, in previous versions, there is a bug related to invokedynamic call sites that prevents this from working.

like image 137
Rafael Winterhalter Avatar answered Oct 17 '22 23:10

Rafael Winterhalter