| Sign In/My Account | View Cart |
Bridging the Gap: J2SE 5.0 AnnotationsIt 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.
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.
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.
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).
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.
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.
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
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;
}
Pages: 1, 2 |