In this article, I show you the power of the product I stumbled upon this week: Project Lombok enables you to modify bytecode at compile code. First, I will detail what features Lombok brings you out-of-the-box. In the second part of this article, I will describe how to extend it to generate you own code.
Introduction
Since the dawn of JEE, complaints have been filed regarding the complexity of coding components. I consider EJB v2 a very good example of this complexity: for just a simple EJB, you have to provide the EJB class itself and a home and an interface for each access type (local and remote). This makes it complex, error-prone and more importantly gives you less time to focus on business code where the real value is. Two initiatives show the will to decrease the amount of boilerplate code needed when coding:
- the Spring Framework's motto is to decrease JEE complexity. The boilerplate code is written once in the Spring framework and only used by projects.
- EJB v3 has taken into account the lessons from Spring and aim to reduce boilerplate code too, making local and remote interfaces unnecessary. Eventually, the next version of the specification will make the local and remote home optional too.
The goal of Project Lombok is exactly the same as the previous initiatives but in order to do so, it uses another mechanism.
Annotation processing
Version 5 of the Java language introduced the concept of annotations, code meta-data that could be processed at compile time and/or runtime. Unfortunately, in JDK 5, processing annotations at compile is a 2-step process. First, you have to run the apt executable to process annotations, perhaps creating or modifying source files, then compile your sources with javac. That was not the best approach, so Java 6 removes apt and make javac able to manage annotations, streamlining the process to obtain a simpler single step computing. This is the path taken by Lombok.
Project Lombok
Lombok’s driving feature is to create code you need from annotations in order to reduce the amount of boilerplate code you have to write. It provides you with the following annotations that will change your code (if not your life) forever:
@Getter
and@Setter
: create getters and setters for your fields@EqualsAndHashCode
: implementsequals()
andhashCode()
@ToString
: implementstoString()
@Data
: uses the four previous features@Cleanup
: closes your stream@Synchronized
: synchronize on objects@SneakyThrows
: throws exceptions
For example, while learning to code the OO way, you were drilled into making your fields private and into writing public accessors to access these fields:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Since this is a bother to write, some (if not all) IDE have a feature to generate the accessors. It has some drawbacks:
- You have to manually remove the accessors if you remove the field
- It clutters your real code with boilerplate code
- Corollary: it takes a real effort to check whether an accessor already exist for a field in a long class
Moreover, creating accessors manually is error-prone: I once searched for hours for a bug in code developed by a fellow developer and finally found the setter was wrong.
Since getter / setter is only meta-data for a field, Lombok’s stance is to treat it like such: in Java, meta-data is managed with annotations. Look at the following code:
import org.lombok.Getter;
import org.lombok.Setter;
public class Person {
@Getter @Setter
private String name;
}
I decompiled the generated class (using JAD) and it creates exactly the same bytecode, only the source code is more concise and less error-prone.
When thinking, I found 3 arguments against using Lombok:
- The first argument against using such a strategy is that you can’t create a protected accessor like that.
You’re wrong, Lombok is configurable:
import lombok.AccessLevel; import org.lombok.Getter; import org.lombok.Setter; public class Person { @Getter @Setter(AccessLevel.PROTECTED) private String name; }
And its true for all the provided annotations! Just look at them.
- The second argument against using Lombok is that you don’t know what it does behind the scene. It’s true, but the same could be said for AOP or CGLIB or whatever framework you’re using.
- The last argument and IMHO the only one valid enough is that it renders debugging more complex: but so is the use of Java dynamic proxies that Spring uses throughout its code, and still many projects use them.
Use and installation
Using Lombok is a 3-step process:
- Put the JAR on the classpath
- Add the annotation you want to use
- Compile with javac
There’s a catch, though. Notice the last statement and the emphasis on javac. Since most (if not all) the developers you and I know focus a little bit on productivity, chances are you’re using a IDE. I do not know about NetBeans and fellows, but my favorite IDE Eclipse does not use javac to compile but its own internal compiler.
Our friends from Lombok thought about that and Lombok is able to hook into Eclipse compiling process too. In order to do so, just launch lombok.jar and follow the instructions on you screen: it will just add 2 lines to your eclipse.ini.
A word of advice (since I made the mistake): if you launch Eclipse with a command that takes parameters, such as a Windows shortcut, these parameters take precedence and eclipse.ini is silently ignored. Just to let you know…
Lombok extensions
To my knowledge, there’s currently only a single extension of Lombok called Morbok.
It lets you create your classical private static final
logger with just an annotation.
The advantage of this is that Morbok automatically uses the fully qualified class name as the name of the logger so no more copy paste error.
The disadvantage is that if you do not use Commons Logging as you logging framework, you have to configure each @Logger
annotation with the framework you want to use, there’s no overall configuration: IMHO, that’s something that could be covered in the next version (is there’s one).
Architecture
First things first, Lombok needs a JDK 6 to compile since annotation processing is done in Java 5 with APT. For now, Lombok hooks into the compiling process immediately after the environment has built the AST for the class.
It then passes the structure thus formed to each of its referenced handlers.
There’s a single handler for each annotation: HandleGetter
for @Getter
, HandlerSetter
for @Setter
and so on.
Handlers are, guess what, responsible for handling annotations.
Extending Lombok
Extending Lombok is a 3-step process:
- Create the annotation.
Since the annotation is used at compile-time, it can be safely be discarded afterwards so its retention policy can be left to its default value (namely
RetentionPolicy.SOURCE
) - Create the handler.
A handler is a class that directly implements
lombok.javac.JavacAnnotationHandler<T extends Annotation>
. Why directly? Because Lombok uses the ServiceProvider service and it’s one of its limitations - Reference the fully qualified class name of the handler in a file named
lombok.javac.JavacAnnotationHandler
under META-INF/services
The real coding takes place in step 2: the interface has a single method handle(AnnotationValues<T> annotation, com.sun.tools.javac.tree.JCTree.JCAnnotation ast, JavacNode annotationNode)
.
Notice the 2nd parameter package? It’s denotes Sun private implementation.
It has some big drawbacks:
- The documentation is sparse if not completely unavailable. You go into unknown territory here
- Since the
com.sun.tools.javac
is not part of the public API, it can change at a moment’s notice. You can break your code with each update - Remember that previously, I talked about this being only good for Java? That’s still true. If you want this new annotation to work under Eclipse, that’s another handler to write
Example
As an example, I coded an embryo for a @Delegate
annotation.
Such an annotation on a field indicates that the declaring class should have the same public methods as the field’s class and each method’s body should be a call to its delegate’s method.
public class Delegator {
@Delegate
private DelegateObject object;
...
}
public class DelegateObject {
public void doSomething() {
...
}
}
The previous code should generate the same bytecode as the following code:
public class Delegator {
private DelegateObject object;
public void doSomething() {
object.doSomething();
}
...
}
As yet:
- it does not handle generics
- the only handler provided is for javac
- it is not configurable
The final implementation is left for the brave readers: original sources are here in Eclipse/Maven format.
Conclusion
Some improvements could quickly be made to Lombok. First, I don’t like the monolithic structure the JAR has. IMHO, it could be nicely decoupled into 3 separate JARs: the Lombok agent itself, the provided annotations and associated handlers and finally, the installer.
Moreover, coding two handlers for each annotation is a lost of time. What if you need to support NetBeans too? Perhaps using the Service Provider is a mistake…
Finally, depending on Sun’s internal compiler API is too big a risk. I think that if Lombok could provide a facade to this API, it could be less risky for enterprise to take this road and the bridging could be made by people who understand the API (the Lombok team) and not base developers (like myself).
All in all, and despite these flaws, Lombok looks like a very promising project that could well mimic Spring’s success. That’s what I wish it anyway because it’s real sideway thinking that brings much added value: good luck for the future!