Custom annotations are a great way to add common reusable behavior to Java applications. But the developers need to follow some guidelines and best practices to identify the areas in the application where using Annotations is a better design solution than other options such as in-line code or using an Interface. John Heintz from New Aspects did a presentation at the recent No Fluff Just Stuff (NFJS) Central Ohio Software Symposium (COSS) on different design techniques for adding behavior to Java custom Annotations.
In the presentation, John gave a walk-through of byte-code and meta programming techniques and an overview of how metadata in Java applications has been managed from the earlier versions of Java, using Properties files, XML, and now Annotations. He mentioned that JSR-175 was formed to provide a metadata facility for the Java language and in Java 5, Annotations were added to the language.
Defining Annotations is similar to defining the Interfaces (Annotations are declared using @Interface type). Annotations can be applied at package level or on a class member (field, method, constructor) or even a method local parameter.
The retention policy, which is an important part of implementing Annotations, is based on how long the data needs to be kept in memory. There are three different types of retention policy:
- Runtime (where data is retained always and accessible through reflection),
- Class (data is kept in byte code, not accessible at runtime), and
- Source level (not kept by compiler).
The recent versions of several popular frameworks like EJB3, Hibernate, Spring, Seam, Struts 2, RIFE and JAX-WS support the Annotations. John discussed three types of Annotation Processing Options:
Generators:
This annotation processing option works by reading the source code and generating new source code or modifying existing source code, and non-source code (XML, documentation, etc.). The generators are typically rely on container or other programming convention and they work with any retention policy.
Some of the generators examples are Annotation Processing Tool (APT) and Processors, XDoclet, Spoon (extensible compiler for Java), and APT-Jelly (which is a templating library). APT doesn't allow you to modify the source code but it's useful for generating auxiliary artifacts (like WSDL, Documentation).
Bytecode Transformers:
These annotation handlers parse the class files with Annotations and emit modified classes and newly generated classes. They can also generate non-class artifacts (like XML configuration files). Bytecode transformers can be run offline (compile time), at load-time, or dynamically at run-time (using JVMTI API). They work with class or runtime retention policy.
Bytecode transformer examples include AspectJ, Spring, Hibernate, CGLib, Javassist, ASM, and BCEL.
Runtime reflection:
This option uses Reflection API to programmatically inspect the objects at runtime. It typically relies on the container or other programming convention and requires runtime retention policy.
Runtime reflection examples are libraries like Java 5+ reflection and Commons Attributes. Testing frameworks like JUnit and TestNG use runtime reflection for processing the Annotations.
John used a sample Java application to demonstrate the implementation of custom Annotations using different design techniques like APT, Javassist, AspectJ, and Reflection (which uses java dynamic proxies). He also showed how to add behavior to classes, fields and methods and talked about annotating Types and Methods with Aspects.
John talked about the benefits of Aspect Processing like ease of implementation (good tool support), fine-grained semantics where it can influence method calls, even field access (unlike reflection and APT), integrating annotations from multiple libraries, and the support for domain-specific abstractions. He suggested that if the java code can be implemented as an interface, then the developers should go with that design instead of using Annotations. He cautioned the developers to not use custom Annotations for everything because a POJO (Plain Old Java Object) is better than a HAJO (Heavily Annotated Java Object). Other best practices mentioned in the presentation are:
- Annotate the highest level of abstraction possible (e.g., class, not method)
- Use sensible defaults and annotate only exceptions to a rule. Also, minimize need for parameters.
- Implement Annotations as complementary to "Convention over Configuration" principle (e.g. in Spring).
- Use Reflection to augment existing frameworks (e.g., testing).
- Utilize Javassist and CGLIB frameworks for generating new objects and for low-level, highly tuned bytecode transformation.
- Use APT for generating non-code artifacts.