Intercepting Annotated Methods...

742 views
Skip to first unread message

Core Dumper

unread,
Sep 29, 2014, 8:48:24 PM9/29/14
to byte-...@googlegroups.com
Hello, I've been going through the docs, trying to see how I might be able to use ByteBuddy to intercept method invocations annotated with a custom annotation.  Since this will be a library, I don't know, or control, what methods will be annotated, i.e.:


@CacheResult

public String getValue() {

    return cpuIntensiveCalculation();

}


@CacheResult

public List<String> getOtherValue(int one, int two) {

    return cpuIntensiveCalculation(one, two);

}


@CacheResult

public ComplexObject getOtherValue(ComplexObject foo) {

    return cpuIntensiveCalculation(foo);

}


Doesn't really seem I can use MethodDelegation…since I don't know what class/method until its annotated. Do I maybe need to generate byte code that extends the annotation interface instead? For example, if I have:



@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface CacheResult {

    String cacheName()

}


class CacheResultImpl implements CacheResult {

    private String name;


    @Override

    public Class<? extends Annotation> annotationType() {

        return Cacheable.class;

    }


    @Override

    public String name() {

        return name;

    }

}


Do I write the *interceptor* logic as part of CacheResultImpl? If so, then how do I invoke the annotated method…as well as access the method name, and arguments?


thanks in advance,


cOrE_dUmPeR


Rafael Winterhalter

unread,
Sep 30, 2014, 12:32:33 AM9/30/14
to byte-...@googlegroups.com
Hi, thank you for your question and for using Byte Buddy.
A method interception has two very distinct parts:

1. A definition what method you want to intercept, represented by a MethodMatcher.
2. A definition of the interception itself, represented by an Interception.

For intercepting methods with a given interception you could use the predefined MethodMatchers.isAnnotatedBy(CacheResult.class) method matcher. From there, you could then easily use the MethodDelegation such as:

MethodDelegation.to(new Object() {
  @RuntimeType
   public Object cacheOrMake(@SuperCall Callable<?> zuper) {
    if(isCached()) {
      return cachedInstance;
    } else {
      return zuper.call();
   }
  }
});

The implementation is of course not proper but if you read the documentation you know what to go for. Also, read the other questions here on the mailing list where similar use-cases of MethodDelegation are discussed.

Ping me back if you have more questions, Rafael

Core Dumper

unread,
Sep 30, 2014, 4:35:27 PM9/30/14
to byte-...@googlegroups.com
Thanks for the advice. After reviewing the ByteBuddy group…and docs, I've come up with the following:


/**********************************
 * ANNOTATION
 **********************************

@Inherited

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface CacheResult {

    String cacheName()

}


/**********************************
 * INTERCEPTOR
 **********************************

public class CacheResultInterceptor


    @RuntimeType

    public static Object intercept(@DefaultCall final Callable<?> zuper, @Origin final Method method, @AllArguments final Object…args) {

        CacheKey key = KeyGenerator.generate(args); // static method call to KeyGenerator

        String cacheName = method.getAnnotation(CacheResult.class).cacheName(); // annotation has cache name

        Cache cache = CacheManager.getCache(cacheName);  // static method call to CacheManager

        Object value = cache.get(key);

        

        if(cachedValue == null) {

            value = zuper.call()

            cache.put(key, result);

        }


        LOGGER.debug("returning value " + value.toString() + " from intercepted method " + method.getName());


        return value;

    }



/**********************************
 * BYTE-BUDDY
 **********************************

Class<? extends Object> dynamicType = new ByteBuddy()

.subclass(Object.class)

.method(isAnnotatedBy(CacheResult.class))

.intercept(MethodDelegation.to(CacheResultInterceptor.class))

.make()

.load(this.getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();



/**********************************
 * USE CASE
 **********************************
class MyService {

    UserDao userDao;

    @CachResult(name = "user_cache")

    public User getById(final String id) {

        return userDao.find(id);

    }

}




I can get this to compile, and fetch my user object…however, the intercept method is never called. I know this because the log statement is never triggered (also, I'm counting method invocations of UserDao.find()). Is there something else I need to do to get the method to intercept?

Is this the right way to write the byte-buddy part?

Also, where in the lifecycle of my application am I supposed to put the byte-buddy section? Before the first reference of @CacheResult? After the last one? If I understand the ByteBuddy lifecycle correctly…I don't need to do anything with the dynamicType reference? I just create the class which triggers byte code injection/creation? This part was a little confusing to me in the docs…though maybe I'm just dense…

thanks in advance,

cOrE_dUmPeR

Rafael Winterhalter

unread,
Sep 30, 2014, 4:53:59 PM9/30/14
to byte-...@googlegroups.com
Hi, i think you got this part mixed up:

new ByteBuddy()
  .subclass(Object.class)

Instead, you need to subclass your actual class that defines the annotated method:

new ByteBuddy()
  .subclass(MyService.class)

and it should work. Byte Buddy is a code generation library. It does nothing more than creating Java classes and there is no life-cycle. In order to apply the cache, you need to create instances of the caching subclass which is created by Byte Buddy:

new ByteBuddy()
  .subclass(MyService.class)

.method(isAnnotatedBy(CacheResult.class))

.intercept(MethodDelegation.to(CacheResultInterceptor.class))

.make()

  .load(this.getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)

  .getLoaded()

  .newInstance()

  .getById("foo"); // is intercepted


You would need to use for example a factory to use these subclassed instances througout your application.

 Non-subclassed instances are not intercepted because MyService is not redefined, it is only subclasses. However, you might want to "inline" this subclass definition into MyService throughout your entire application such that the cache is always active, even for instances created by new MyService() which you can do by:

new ByteBuddy()
  .rebase(MyService.class)

while not altering any of the other code. The problem of this approach is that you cannot redefined loaded classes in Java which is why you need to do this before application startup, for example from a build-tool. I am currently working on support for load-time enhancement using a Java agent which is probably interesting for you. I hope to realease this version sometime in december.

Best, Rafael



Am Dienstag, 30. September 2014 02:48:24 UTC+2 schrieb Core Dumper:

Core Dumper

unread,
Sep 30, 2014, 5:19:51 PM9/30/14
to byte-...@googlegroups.com
Ahh, ok, I get it now. 

Yes, my problem is that since I'm trying to write a library…I don't know which classes or methods will be annotated. So I was trying to use ByteBuddy in the way I use other libraries which allow separate development of *what* gets weaved….and *where* it gets weaved. This of course implies the library has to do some discovery at load-time…or even at compile-time.

In any case…cool library. I like the API which is a lot easier to understand than some of the other, seemingly arcane, solutions for byte code manipulation.

cheers,
/m

Rafael Winterhalter

unread,
Sep 30, 2014, 5:25:19 PM9/30/14
to byte-...@googlegroups.com
You will like version 0.4 then, it allows you to define a Java agent with a sort of TypeMatcher where you can then say something like:

public static void premain(String args, Instrumentation inst) {
  AgenBuilder.of(inst)
    .intercept(annotatedBy(Cached.class))
    ....
}

This is very preliminary API but this will then just redefine any class with such an annotation during load time.

Best, Rafael

Core Dumper

unread,
Sep 30, 2014, 5:31:08 PM9/30/14
to byte-...@googlegroups.com
Cool, looking forward to playing with it.

/m
Reply all
Reply to author
Forward
0 new messages