Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

After and before constructor interceptor

I know how to create a BEFORE constructor interceptor:

return builder.constructor(isDeclaredBy(typeDescription))
   .intercept(MethodDelegation.to(constructorInterceptor)
   .andThen(SuperMethodCall.INSTANCE));

I know how to create an AFTER constructor interceptor:

return builder.constructor(isDeclaredBy(typeDescription))
   .intercept(SuperMethodCall.INSTANCE
   .andThen(MethodDelegation.to(constructorInterceptor)));

With the following interceptor:

public void intercept(@Origin Constructor<?> constructor) {
    System.out.println("intercepted " + constructor.getName());
}

However I don't know how to create a before/after interceptor. Here is what I tried (naive approach based on what already worked for methods):

return builder.constructor(isDeclaredBy(typeDescription))
    .intercept(MethodDelegation.to(constructorInterceptor));

With this method delegate:

public void intercept(@Origin Constructor<?> constructor, @SuperCall Callable<?> zuper) throws Exception {
    System.out.println("before " + constructor.getName());

    zuper.call();

    System.out.println("after " + constructor.getName());
}

With this setup I get:

java.lang.ClassFormatError: Bad method name at constant pool index 23 in class file com/flow/agent/test/Foo$auxiliary$syFGNB3u

Full stack trace:

java.lang.IllegalStateException: Error invoking java.lang.ClassLoader#findClass
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Resolved.loadClass(ClassInjector.java:392)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.inject(ClassInjector.java:201)
    at net.bytebuddy.agent.builder.AgentBuilder$InitializationStrategy$SelfInjection$Dispatcher$Split.register(AgentBuilder.java:1017)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$Simple$Resolution.apply(AgentBuilder.java:2795)
    at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:3081)
    at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
    at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ...
Caused by: java.lang.ClassFormatError: Bad method name at constant pool index 23 in class file com/flow/agent/test/Foo$auxiliary$syFGNB3u
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Resolved.loadClass(ClassInjector.java:388)
like image 432
user3408654 Avatar asked Dec 23 '15 10:12

user3408654


1 Answers

The Java virtual machine's validator requires a hard-coded invocation of another constructor from any constructor that is implemented. Therefore, using @SuperCall for implementing around-advice does unfortunately not work. As a matter of fact, the @SuperCall annotation cannot work with constructors alltogether. (Ideally, Byte Buddy would catch this attempt and throw a more readable excepion, I will fix that for the next version of the library.)

What you can do is to define the interceptor like the following:

public class Interceptor {
  public void before(@Origin Constructor<?> constructor) {
    System.out.println("before " + constructor.getName());
  }
  public void after(Origin Constructor<?> constructor) {
    System.out.println("after " + constructor.getName());
  }
}

using an interception like:

MethodDelegation.to(constructorInterceptor).filter(named("before"))
                .andThen(SuperMethodCall.INSTANCE
                .andThen(MethodDelegation.to(constructorInterceptor))
                                         .filter(named("after")))

This will first invoke the before method, then call the super constructor and then invoke the after interceptor.

Of course, you might want to be able to transport a value from before to after. For this purpose, Byte Buddy does not yet offer a great way of doing things. (I still hope for an enhancement in the JVM itself to take advantage of. This VM limitation also hits people using method handles and is often mentioned as an unfortunate disatvantage by people working with the VM.)

For now, you can always define a ThreadLocal field in the interceptor where you store a value from before that you read during after. (In a single-thread environment, you can even drop the ThreadLocal and use a simple field.) One reason that I have not yet targeted this is that most constructor interceptions are not requiring around-adivce. If you state a more detailed use case I might be able to help you further.

like image 143
Rafael Winterhalter Avatar answered Sep 20 '22 17:09

Rafael Winterhalter