ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Bridging the Gap: J2SE 5.0 Annotations Bridging the Gap: J2SE 5.0 Annotations

by Kyle Downey
10/06/2004

It takes a long time for the Java community to fully absorb a major new JDK release; it seems to take about two more releases after a brand new version of the JDK before everything settles down. Application server vendors don't always fully support dot-zero releases right away; IDE, profiler, and other tool support can lag; many new bugs have to be fixed; and businesses are leery of building on anything so new. Those of us on MacOS X who have not snagged a beta DVD have to wait until Tiger comes out in the first half of 2005 to use Java 5.0. The latter obstacle got me interested in how to co-opt some J2SE 5.0 features earlier rather than later.

In the interim, we have time to learn and experiment--probably about a year, depending on how conservative you are about new technology. This article talks about strategies to bridge yourself to one new 5.0 feature without making the full leap: annotations.

What Are Annotations?

JSR-175, part of the 5.0 specification in the Java Community Process, describes a proposed "metadata facility" for Java. Perhaps a more apt description would be metacoding: annotations are code fragments that elaborate upon and describe Java classes, methods, and fields. They're implemented as a special kind of interface and can be packaged, compiled, and imported like any other class.

Markers

A marker annotation is a short-form where the presence of the annotation provides all the information you'd need. For example, we'll consider how a hypothetical OODBMS might use annotations to mark code. Let's say the designers decided they wanted to make their persistence mechanism totally independent of Java serializability, and thus wanted to avoid using the Serializable interface and the transient reserved word. Two examples of markers might be having one to indicate that a class is persistent and another to mark a field as transient.

import oodbms.annotation.Persistable;
import oodbms.annotation.Transient;

@Persistable public class Foo { 

    private String field;
    @Transient private String tmpField;
}

The OODBMS could then recognize a class used in the context of a transaction as a persistent one and store and update it accordingly. It might also respect the class's wishes by ignoring a temporary field value marked as @Transient.

Single value

Most annotations need to take parameters, but for the special case of a single value, the annotation spec provides a shorthand. Let's say we want to declare a version number for our persistent class. Here's one way:

Import oodbms.annotation.Persistable;
Import oodbms.annotation.Version;

@Persistable 
@Version ("1.0.0")
public class Foo { }

In general, a single-parameter annotation takes the form, @Annotation(param-value). It's a slightly more compact rendering of @Annotation(value=param-value).

Normal annotations

Most of the time, you want to provide more structured information about a piece of code, more than just a single attribute. In this case, annotations let you specify one or more name-value pairs. The values can be Java primitives (ints, Strings, doubles, boolean values, etc.) or other annotations. This lets you create quite complex structures while maintaining type safety, one of the benefits of the new annotations specification over traditional Doclet tags. Revisiting our version number, we might want to enforce a structure to it:

@Persistable
@Version(major=1, minor=0, micro=0)
public class Foo { }

The compiler and a syntax-aware IDE with support for 5.0, like IntelliJ IDEA 4.0 or Eclipse 3.1 (in beta), can recognize proper use of our new Version annotation immediately, for instance, recognizing a misspelling of one of the fields.

Arrays

Annotations can have multi-valued fields by using the array syntax. For instance, let's say we wanted to replace the Javadoc author fields, which allow multiple values:

@Authors({
    @Author("Jane Doe")
    @Author("John Doe")
})

You can nest arrays inside arrays and complex annotations inside arrays. If you can't represent your metacode as an annotation, you probably are trying to do too much with it. The syntax is quite powerful.

Declaring Your Own Annotations

An annotation is declared in a struct-like form with named and typed fields, nested enumerations, if needed, and—like interfaces—no code. They're pure data structures that can ultimately get boiled down to extra JVM bytecode attributes for the methods, fields, and types they annotate.

public @interface HelloAnno {
    enum Scope { LOCAL, WORLD }
    String name();
    Scope scope();
}

In the declaration, an annotation can refer to another annotation as well:

import oodbms.annotation.Version;
	
public @interface HelloAnno {
    enum Scope { LOCAL, WORLD }
    String name();
    Scope scope();
    Version version();
}

And they can have default values:

import oodbms.annotation.Version;
	
public @interface HelloAnno {
    enum Scope { LOCAL, WORLD }
    String name() default "Foo";
    Scope scope() default Scope.LOCAL;
    Version version();
}

The above definition could thus be defined in two ways:

@HelloAnno(version=@Version(major=1,minor=0,micro=0))
,

which takes advantage of the default values, and the following, which sets explicit values:

@HelloAnno(name="Bar", scope=Scope.WORLD, 
    version=@Version(major=1,minor=0,micro=0))

One more declaration trick: For a single-valued annotation, the compiler assumes the single-field name will be value, like so:

public @interface HelloAnno {
    String value() default "world";
}

// usage: value gets assigned "world"
@HelloAnno("world");

// also valid
@HelloAnno(value="world")

// and this works too, thanks to the default!
@HelloAnno

C'mon, Arthur! Get meta with me!

Annotation declarations can have their own annotations, which I guess would be meta-metacoding. The specification calls them "Meta Attributes." For instance, the @Target annotation specifies what kinds of Java language elements your application is allowed to modify: @Target(ElementType.TYPE) would restrict it to modifying enumerations, classes, or interfaces; @Target(ElementType.CONSTRUCTOR) limits it to constructors; and so on.

The @RetentionPolicy annotation tells the compiler what to do with the annotation: SOURCE discards it, CLASS embeds it in the classfile (the default), and RUNTIME makes it available via reflection.

Let's say our OODBMS has a very smart class-loader that can look at bytecodes as they enter the JVM, so that we don't really need reflection. Our Version annotation might look like the following:

package oodbms.annotation;

import java.lang.annotation.*;

@RetentionPolicy(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Version {
    int major();
    int minor() default 0;
    int micro() default 0;
}

Wrapping up the Annotations Overview

In some ways, JSR-175 does not go as far as it might have gone in changing Java. Marker interfaces like Serializable and Cloneable, the transient attribute for fields, all of Javadoc, serialVersionUID, and probably more could have been redone as attributes. Documentation, details of serialization, assertions, and more are all examples of metacode, but such a radical change would have been even harder to absorb. I believe this restraint was wise.

Annotations Without 5.0

With that covered, you're probably pretty interested in using this feature. But if we don't have 5.0, how can we use part or all of the annotations facility for use in our programs?

XDoclet

XDoclet and its implementation of attribute-oriented programming has been the subject of whole books and many articles, so I won't do much more than a sketch here for comparison purposes.

XDoclet is a Javadoc tag-based approach to annotating code. Plugins for the Xdoclet tool support new features. The metadata structure is more flat and less object-oriented than 5.0 annotations. You have to tell your IDE about new tags if it recognizes Javadoc tags and highlights unknown ones (like IntelliJ), and if you, say, pass an integer argument rather than a string, you're out of luck until you run XDoclet on your project, and there's no guarantee your problem will even be caught then.

On the other hand, XDoclet has been applied to enough projects that there's at least some body of knowledge regarding how best to use it. It also has the great advantage of being orthogonal to the Java language rather than part of it; the two could evolve separately and at different speeds. And if Sun had adopted XDoclet, existing libraries and UI tools that deal with Java code could have stayed the same.

Commons attributes

The fine folks at Apache's Jakarta project offer Commons Attributes. This package provides a very close approximation of 5.0 annotations for users of earlier JDKs. The only syntactical differences are that they start with a @@ instead of @ and are embedded in Javadoc instead of part of the language. Attributes are implemented as classes, just as in 5.0, so you get a strong degree of type-safety at post-processing time. Our Version attribute from 5.0 would look like this:

public class Version {
    public Version() { }

    public int getMajor() {
        return major;
    }

    public void setMajor(int major) {
        this.major = major;
    }

    public int getMinor() {
        return minor;
    }

    public void setMinor(int minor) {
        this.minor = minor;
    }

    public int getMicro() {
        return micro;
    }

    public void setMicro(int micro) {
		this.micro = micro;
    }

    private int major;
    private int minor;
    private int micro;
}

And would be used this way:

/**
 * @Persistable
 * @@Version(major=1,minor=0,micro=0)
 */
public class Foo { }

Commons Attributes provides an attribute compiler, integrated with both Ant and Maven, that you can use to generate the final Java class files including your annotations. It also provides a runtime interface for reading annotations from classes.

It's the most well-developed alternative out there, and the easy transition to 5.0 is a big plus.

JBoss annotations support

Jboss AOP has another annotation implementation in JDK 1.4.x. Its compiler, based on Commons Attributes, generates code that is bytecode-compatible with 5.0 Annotations but recognizable to a 1.4 VM. Bill Burke covers this and a lot more in the article Aspect Oriented Annotations.

JAM+SGen

Cedric Beust at BEA has another Javadoc-based tool called SGen.

Unfortunately, there's not much documentation out there for this package, and it's less mature than Xdoclet (that's both good and bad--it doesn't have legacy issues, but it's gotten less of a workout from developers to shake out bugs and design issues). Its strength over and above 5.0 annotations and alternatives, though, is integration of metadata and code generation. It's very easy to write code generators that are driven by either Javadoc-based or 5.0 annotation-based metadata.

P.Anno

I wrote the P.Anno ("piano") library to parse 5.0 annotations as a standalone language. I was interested in seeing whether there might be value in adding annotations to scripting languages supported by Apache's BSF (Bean Scripting Framework). Since annotations have their own grammar, it made sense to create a 100% standalone library just to handle them, a library that could be plugged into editors, Javadoc processors, and other programmer-oriented tools. It's also a good learning tool for playing with the full annotations specification (as opposed to alternate dialects based on plain Javadoc) without using 5.0.

The parser/lexer itself uses SableCC to implement the grammar.

Here's how you might read our version attribute using P.Anno:

Reader r = new StringReader("@Version(major=1,minor=0,micro=0");
AnnotationParser parser = new AnnotationParser();
Collection annos = parser.parse(r);
Iterator annoIter = annos.iterator();

Annotation anno = (Annotation)annoIter.next();
System.out.println("Version: " + anno.getValue("major") + "." + 
	anno.getValue("minor") + "." + anno.getValue("micro"));

Conclusion

The impact of annotations, whatever it ends up being, is maybe best viewed through the lens of the platform's evolution. We're now on the fifth major revision of the JDK. Looking back further, Java is also a branch of a family of C-like languages, so even JDK 1.0 introduced new ideas, on top of an existing body of knowledge, about what constituted good programming.

Some introductions since then, like Collections and logging, certainly triggered conflicts. In those examples, it was because they solved problems already solved by fairly well-accepted packages (JGL and log4j), and some people naturally questioned why Sun was reinventing those particular wheels. But notably, no one questioned their utility or how to use them; the best practices had been worked out already, in a sense.

To take one example, I expect this will be how the util.concurrent features being migrated into J2SE 5.0 will evolve: relatively easy acceptance and adoption where needed. People who needed those features were probably already using Doug Lea's util.concurrent library. Generics may or may not go so easily--there's a huge body of knowledge from C++ templates, but it is new to Java. Plus, the implementations are sufficiently different that there may be a good bit of experimentation before we get it right.

Features like Reflection and standardized, integrated threading support, from the very beginning, have been different sorts of beasts. Reflection has been abused and put to creative uses, and it has taken a long time to establish how best to use it. Perhaps this is because the closest analog to it was RTTI in C++, which at the time was still fairly new and not very well accepted. It takes time for the community to develop a body of knowledge about new ways of solving problems. As to threading, well, let's just say I know of one disastrous project which constantly crashed when it spawned multiple server threads--from a scrollbar event handler.

I predict annotations will have the same teething pains. This is not to say no one knows what to do with it, or that there are no clear antecedents (XDoclet is probably the closest in wide use) to look to for guidance. Certainly, other languages had threading support and metaprogramming facilities before Java as well, and attribute-oriented programming is not entirely new either. But if the disputes over how to apply annotations in EJB 3.0 on The Server Side's forums are any guide, it's not clear to everyone what constitutes elegant versus abusive.

The transitional attribute-oriented programming tools discussed in this article can give you a workbench for playing with these ideas before the widespread adoption of J2SE 5.0. And if you're feeling adventurous, by all means, grab J2SE SDK 5.0 and start building new designs enhanced by annotation support. The successes and failures from our experiments will help everyone learn how to use this new and powerful language feature.

Kyle Downey is a part-time Java consultant and founder of Amber Archer Consulting


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.