; last updated - 6 minutes read

Speed'em up!

Last week I showed you how to utilize the Javassist library to mix up several Java classes into one. That's more or less the concept of "mixins" most modern programming languages offer.

The most important disadvantage of my approach is it mixes the classes at run time. Your compiler can't help you. Neither can your IDE. So my idea may deliver the features of a mixin, but it doesn't look nor feel like a mixin. You need an interface or a type cast to access every method of the mixin - something you don't need in, say, Scala.

There's nothing I can do about that. Java's just the way it is. If you aren't happy with it, use a more modern language :).

What we can fix is the second disadvantage: performance. That's what we'll do today.

Let's get down to real byte code!

Javassist allows you to do real byte code manipulation. Last week we just added a proxy to a class to change it. While this approach works it adds an additional layer of complexity to the class. The price is a huge performance penalty. Plus I consider the resulting code pretty hard to read.

This week we'll build a class from scratch. Alternatively we could use the same technique to take an existing class and to manipulate it.

More precisely, we can take an existing class and create a new class by modifying it. The original class remains unchanged. Actually, that's good news: there's no security risk associated with Javassist.

Creating a class - doesn't this involve complicated tasks like creating a constant pool? Starting a file with CAFEBABE - the famous magic number of Java classes - may be simple, but anything beyond that... but wait. That's what Javassist does for you. We don't even have to know about byte code. Javassist allows you to create a class and to populate it with fields, methods and algorithms without even knowing there's something like byte code.

The example: barking dogs helping me to implement AngularFaces

Remember the dogs example of my last post. Dogs bark, but that's not a unique trait of dogs. Young wolves bark, too. Dogs also like to fetch sticks thrown by humans. Probably there's also another species out there with a similar trait, so let's say being a "Fetcher" is a trait of dogs possibly shared by other animals. So it may be worth the pain to define two reusable classes, "Barker" and "Fetcher". Dogs contain both traits, so we may think of a dog as a mixin of both classes. Wolves, in contrast, implement only one of the traits: Barker (at least if it's a young wolf).

Of course, in real world I hardly ever program a dogs behavior. My goal is to simplify the AngularFaces library. I want to extend a lot of JSF components in a uniform way, but I can't due to Java's restrictions. I need to add the ngModel property to both InputText and SelectOneMenu, but I can't do so using inheritance because both classes already belong to an inheritance tree. So if talking about dogs, wolves, sticks and barking sounds weird to you, keep in mind I'm really talking about input fields, check boxes, UIComponent and ngModel.

Create a class from scratch

Creating a class is simple:

ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("DogImpl");

Next we want the new class to derive from another class:

CtClass barker = pool.get("de.beyondjava.jsf.sample.mixins.Barker"); cc.setSuperclass(barker);

Making the class implement one or more interfaces is almost as simple:

CtClass dog = pool.get("de.beyondjava.jsf.sample.mixins.Dog"); cc.setInterfaces(new CtClass[] { dog });

So far everything's been simple, hasn't it? But we've already produced fairly sophisticated byte code. The good news we don't have to know about it. Instead we can focus on our task. Mixing a Fetcher into the DogImpl is simply done by adding a Fetcher attribute and delegating every call that can be executed by a Fetcher to this attribute.

So next we'll add an attribute to the class:

CtClass fetcher = pool.get("de.beyondjava.jsf.sample.mixins.Fetcher"); CtField fetcherField = new CtField(fetcher, "fetcher", cc); cc.addField(fetcherField, "new de.beyondjava.jsf.sample.mixins.Fetcher()");

There's something new here. The new field is automatically initialized with new Fetcher(). We defined the initializer using pure Java. Javassist contains a simple Java compiler translating the string to byte code. That's why we can generate byte code without having to learn it. However, according to the documentation Javasisst's compiler can't compile every valid Java code.

Implementing the method is straight forward:

CtMethod fetchMethod = CtNewMethod.make("public void fetch() { fetcher.fetch(); }", cc); cc.addMethod(fetchMethod);

Again, we defined the method by just giving the Java code, leaving it to Javassist to puzzle out what the byte code might look like.

We're almost done. All we have to do is to convert the class to an ordinary Java class before we can use it:

Class dogClass = cc.toClass(); Dog d = (Dog) dogClass.newInstance(); d.bark(); d.fetch();

Mixing arbitrary classes

I've prepared a more more versatile (and much more abstract) version of this example at GitHub repository. It allows you to mix arbitary classes. Granted, at the time of writing only methods without parameters are supported, but this is work in progress.

Measuring performance

Of course it takes some time to create and compile the class. After that, the class is every bit as fast as every hand-written Java class.

Javassist even allows you to write classes to files. So you don't have to compile these classes every time you start the application. That's similar to what a compiler does.

Conclusion

Creating a mixin at runtime is a somewhat limited concept. Nonetheless it works surprisingly well and it's even blazingly fast. Javassist has a real edge over Java's dynamic proxy when it comes to performance. I even consider the version I showed you today simpler and easier to maintain than its proxy counterpart.

But still, nothing beats the real thing. Maybe I'll convert AngularFaces to a Scala project just because of Scala's traits.


Comments