Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.
See all our reports

How my new friend Byte Buddy enables annotation-driven Java runtime code generation

ByteBuddy

Last week, we looked into Java’s strong and static type system. We stipulated that such types make our code more expressive, but at the same time limit third-party frameworks in offering POJO-centric APIs.

We worked out that Java reflection is a great way of interacting with user code, but it comes at the price of losing type-safety. In order to retain type-safety, perhaps it’s better to use code generation for the creation of subclasses of a given user class at runtime?  For these classes, we override any method to implement our framework logic without requiring a user to depend on framework types.

I suggest you read the first part of this article if you haven’t read it already. I hope it helps you to get a feeling for the kind of problem code generation wants to solve and when you should rather use reflection.

Are we there yet?

We all know how to define a Java class (I hope), so there is nothing new I can teach you here. However, we normally express these class definitions in some JVM language before asking a compiler to translate our narratives into bytecode.

Java bytecode is a binary format but, other than that, the Java programming language and its bytecode are fairly similar. This is no surprise since the Java language sort of drives the evolution of the bytecode instruction set. If you want to know more about bytecode, there is a great report about Mastering JVM Bytecode available here on RebelLabs that will teach you all the tricks.

While directly working with Java bytecode gives you full control over the generated classes, it is also quite cumbersome to deal with. Bytecode is served to you without certain conveniences like auto-boxing or high-level control flows. Also, writing bytecode requires you to do a lot of boring stuff such as counting operand stack sizes and computing stack map frames. Wouldn’t it be great if you could skip all this in order to focus on writing your actual runtime code?

Of course it would. And for this reason, even the Java class library comes with limited support for defining runtime classes. These classes are known as JDK proxies and allow the runtime definition of a class which implements any set of interfaces.

An invocation of any interface method is then forwarded to an invocation handler which you are to implement and provide. When discussing JDK proxies, the emphasis lies on the implementation of interfaces. You cannot extend any class with these built-in Java proxies.

For our security framework from the last blogpost, this would mean that we could only secure interface methods and not just methods of any type. That’s a rather lame restriction. For this exact reason, cglib was released as an independent library shortly after the introduction of the JDK proxies.

Cglib works quite similar to the JDK proxies but is able to proxy any method by creating dynamic subclasses instead of only implementing interfaces. Cglib is still a popular choice and frameworks such as Spring rely on it for some of their functionality.

However, despite its popularity, cglib is no longer under active development and in the last years even bug fixes were only released occasionally. This is in particularly problematic since cglib consequently does not support new bytecode features such as Java 8 default methods.

Due to these unresolved issues with cglib, over the years an increasing number of projects decided to move away from the library. And at least after the Hibernate project made javassist its first choice, this Java assistant library became an obvious alternative for the mainstream.

To begin with, javassist comes with a proxy library that mainly imitates the functionality of cglib. Beyond that, javassist unfolds its full potency by offering a runtime Java compiler which allows the definition or redefinition of about any Java class by simply providing strings of Java code.

While it sounds amazing, writing code inside a String can quickly get out of hands, especially if you need to mix a compiled functionality with dynamic one. The code on the image below is a real-world example of what you can get into with javassist.
javassist-horrors

Moreover, while Java source code is a great and intuitive way of expressing a Java class; unfortunately, as of today, javassist is still a one-man project even though Red Hat started to sponsor the library. And with the steady advancement of the Java language, the javassist compiler increasingly lags behind the one of the JDK.

As a result, you still need to think bytecode even when writing javassist-flavoured Java code. As of this writing, you are still required to,  for example, perform explicit value boxings of primitive types. And guess what? With the introduction of new language features in Java 8, the gap between both compilers just became even bigger.

A little less complaining, please?

Now, it is easy to point fingers at the shortcomings of other people’s libraries, but how could we improve the situation? Both cglib and javassist were created early in this millennium and their APIs were built around the language features that Java had to offer back in these days.

One of the more significant innovations introduced after the inception of these libraries are annotations. It’s somewhat ironic that code generation is mainly used to implement annotation-based APIs while no code generation library is itself built with one. For this reason, I took on the challenge and wrote another library which walks down this path.

The library is called Byte Buddy and it uses annotations and a domain specific language for achieving its ambitions. Doing so, a runtime class can be created like in this code snippet:

new ByteBuddy()
  .subclass(Object.class)
  .method(named(“toString”))
  .intercept(MethodDelegation.to(ToStringInterception.class))
  .make()

If you can approximately grasp the meaning of this code, the library turned out as I hoped. One remaining mysterious bit might be the intercept method which takes a method delegation as its argument. When receiving such a method delegation, Byte Buddy will forward invocations of methods that match the given naming constraint to a static method in the given class. This target class and method could for example be implemented like this:

class ToStringInterception {
  public static String intercept(@Origin Class<?> type) {
    return type.getSimpleName();
  }
}

With the interceptor above, any invocation of the toString() method on an instance of the dynamic class would result in a delegation to the sole static method. By using the @Origin annotation, the method receives a class reference as its only argument. Consequently, at the runtime the return value of a toString() invocation would be the intercepted class’s simple name.

This is only a scratch on the surface of Byte Buddy’s API but we won’t go into any library specifics here. But if you care, check out the official documentation on Byte Buddy’s website which reveals all the magic.

Regardless, in order to demonstrate that applied code is suited for every-day programming, let us use Byte Buddy to implement the SecurityLibrary that we mentioned in the first part of this article. As we will see, an immediate implementation that simply stores the logged-in user in a static field does not require more than only a few lines of code:

class ByteBuddySecurityLibrary implements SecurityLibrary {
 
  public static String currentUser = “admin”;
 
  @Override
  public  Class<? extends T> secure(Class type) {
    return new ByteBuddy()
      .method(isAnnotatedBy(Secured.class))
      .intercept(MethodDelegation.to(ByteBuddySecurityLibrary.class))
      .make()
      .load(type.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
      .getLoaded();
  }
 
  @RuntimeType
  public static Object intercept(@SuperCall Callable<?> superMethod,
                                 @Origin Method method) throws Exception {
    if (!method.getAnnotation(Secured.class).requiredUser().equals(currentUser)) {
      throw new IllegalStateException(method + " requires appropriate login”);
    }
    return superMethod.call();
  }
}

Similar to the first example, we use Byte Buddy to intercept methods in order to delegate their invocation to a framework method. This time, we intercept methods that are annotated by the Secured annotation.

For these methods, we compare the annotation value to the currently logged-in user and throw an exception if this user does not equal the user that is required by the annotation. Otherwise, we invoke the original method.

In Java, it is not normally possible to invoke a super method from outside the instance on which the super method is invoked. To overcome this limitation, Byte Buddy automatically creates a proxy class which is defined similarly to a non-static inner class of the Java programming language. Doing so, a super method can be invoked as a Callable even from outside the intercepted method.

Hurray! We just defined a class at runtime and did it without using a compiler. Champagne! Let us call out a week of celebration and meet here again next week when we compare the performance of different code generation libraries. Cheers!

Write back at me or ask any questions about Byte Buddy, the best size of towel for the beach or answers about the origins of the universe in the comments below, or find me on Twitter @rafaelonjava.

 

Update! The final part of the series: Testing the performance of 4 Java runtime code generators: cglib, javassist, JDK proxy & Byte Buddy is published.

  • Brian Oxley

    What is @Context? I didn’t find it in bytebuddy from Maven central (0.2.1).

  • http://rafael.codes/ Rafael Winterhalter

    I renamed the @Context annotation to @Origin before the release, but apparently the “beta names” are still stuck in my head. Thanks for pointing this out!

  • Peter

    Have a look at https://github.com/verhas/djcproxy being there for a seat or so.

  • Peter

    Year or so …

  • marvlush

    Don’t think the example above compiles:

    @Override
    public Class secure(Class type) {
    return new ByteBuddy()
    .method(isAnnotatedBy(Secured.class))
    .intercept(MethodDelegation.to(ByteBuddySecurityLibrary.class))
    .make()
    .load(type.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
    .getLoaded();
    }

    the “make()” reports “The method make() is undefined for the type ByteBuddy.MethodAnnotationTarget” in my IDE.