Annotations in Tiger, Part 2: Custom annotations

In the first article in this series , I explained what metadata is, why it's valuable, and how to use the basic built-in annotations introduced in J2SE 5.0 (aka Tiger). If you're comfortable with these concepts now, you might already be thinking that the three standard annotations Java 5 offers aren't especially robust. You can do only so much with Deprecated , SuppressWarnings , and Override . Fortunately, Tiger also lets you define your own annotation types. In this article, I'll take you through this relatively simple process with some examples. You'll also find out how to annotate your own annotations and what you gain by doing so. My thanks to O'Reilly Media, Inc., which has graciously allowed me to use the code sample from the annotations chapter of my book on Tiger for this article (see Resources ).

Defining your own annotation type

With the addition of a little syntax (and Tiger has added plenty of syntactical constructs), the Java language supports a new type -- the annotation type . An annotation type looks a lot like an ordinary class, but it has some unique properties. Most notably, you can use it with the at sign (@ ) in your classes to annotate your other Java code. I'll walk you through the process piece by piece.

The @interface declaration

Defining a new annotation type is a lot like creating an interface, except that you precede the interface keyword with the @ sign. Listing 1 shows an example of the simplest possible annotation type:


Listing 1. A very simple annotation type

package com.oreilly.tiger.ch06;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
public @interface InProgress { }

 

Listing 1 is pretty self-explanatory. If you compile this annotation type and ensure that it's in your classpath, you can then use it on your own source code methods to indicate that a method or class is still in progress, as in Listing 2:


Listing 2. Using your custom annotation type

@com.oreilly.tiger.ch06.InProgress
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

 

You use the annotation type in Listing 1 exactly the same way you use the built-in annotation types, except that you indicate the custom annotation by both its name and package. Of course, normal Java rules apply, so you can import the annotation type and refer to it as simply @InProgress .

Don't miss the rest of this series

Be sure to read, "Part 1" of this series, which introduces annotations in Java 5.0.

Adding a member

The basic usage I've just shown you is far from robust. As you'll remember from Part 1, annotation types can have member variables (see Resources ). This is useful, especially when you begin to use annotations as more-sophisticated metadata, not just raw documentation. Code-analysis tools like to have lots of information to crunch, and custom annotations can supply that information.

Data members in annotation types are set up to work using limited information. You don't define a member variable and then provide accessor and mutator methods. Instead, you define a single method, named after the member, that you want to allow for. The data type should be the return value of the method. The concrete example in Listing 3 should make this somewhat confusing requirement more clear:


Listing 3. Adding a member to an annotation type

package com.oreilly.tiger.ch06;

/**
* Annotation type to indicate a task still needs to be
* completed.
*/
public @interface TODO {
String value();
}

 

As odd as Listing 3 might look, it's what you need in annotation types. Listing 3 defines a string named value that the annotation type can accept. You then use the annotation type as in Listing 4:


Listing 4. Using an annotation type with a member value

@com.oreilly.tiger.ch06.InProgress
@TODO("Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

 

Again, not much is tricky here. Listing 4 assumes that com.oreilly.tiger.ch06.TODO has been imported, so in the source, you don't prefix the annotation with its package name. Also note that Listing 4 uses the shorthand approach: You feed the value ("Figure out the amount of interest per month") into the annotation without specifying the member-variable name. Listing 4 is equivalent to Listing 5, which doesn't use the shorthand:


Listing 5. "Longhand" version of Listing 4

@com.oreilly.tiger.ch06.InProgress
@TODO(value="Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

 

Of course, we're all coders, so who wants to mess with the longhand version? Take note, though -- the shorthand version is available only if the annotation type has a single -member variable named value . If you don't meet this condition, you lose the shorthand feature.

Setting default values

What you've seen so far is a good start, but you have plenty of ways to spruce it up. Probably the next addition you'll think of is to set some default values for the annotation. This is nice when you want users to specify some values, but they need to specify other values only if they differ from the default. Listing 6 illustrates both this concept and its implementation with another custom annotation -- a fuller-featured version of the TODO annotation type from Listing 4 :


Listing 6. Annotation type with default values

package com.oreilly.tiger.ch06;

public @interface GroupTODO {

public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };

Severity severity() default Severity.IMPORTANT;
String item();
String assignedTo();
String dateAssigned();
}

 

The GroupTODO annotation type in Listing 6 adds several new variables. Note that this annotation type doesn't have a single-member variable, so you gain nothing by naming one of the variables value . Any time you have more than one member variable, you should name them as precisely as possible. You don't get the benefit of the shorthand syntax shown in Listing 5 , so you might as well be a little more verbose and create better self-documentation for your annotation type.

Another new feature that Listing 6 demonstrates is that the annotation type defines its own enumeration. (Enumerations -- usually just called enums -- are another new feature of Java 5. This isn't anything remarkable, or even specific to annotation types.) Then, Listing 6 uses the new enumeration as the type for the member variable.

Finally, back to the subject at hand -- default values. Establishing them is pretty trivial. You add the keyword default at the end of the member declaration, and then supply the default value. As you might expect, this must be the same type that you declared for the member variable. Again, this isn't rocket science -- just a little bit of a lexical twist. Listing 7 shows the GroupTODO annotation in action, in a case in which severity is not indicated:


Listing 7. Taking advantage of default values

  @com.oreilly.tiger.ch06.InProgress
@GroupTODO(
item="Figure out the amount of interest per month",
assignedTo="Brett McLaughlin",
dateAssigned="08/04/2004"
)
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

 

Listing 8 shows the same annotation in use, this time with a value supplied for severity :


Listing 8. Overriding default values

  @com.oreilly.tiger.ch06.InProgress
@GroupTODO(
severity=GroupTODO.Severity.DOCUMENTATION,
item="Need to explain how this rather unusual method works",
assignedTo="Jon Stevens",
dateAssigned="07/30/2004"
)
public void reallyConfusingMethod(int codePoint) {
// Really weird code implementation
}

 


Annotating an annotation

Before closing the book on annotations (at least in this series), I'll deal briefly with annotating annotations. The set of predefined annotation types you learned about in Part 1 have a predetermined purpose. However, as you move into writing your own annotation types, the purpose of your annotation types isn't always self-evident. In addition to basic documentation, you'll probably write types that are specific to a certain member type, or perhaps a certain set of member types. This requires you to supply some sort of metadata on your annotation type, so that the compiler can enforce the annotation's intended functionality.

Of course, annotations -- the Java language's choice for metadata -- should immediately come to mind as the solution. You can use four predefined annotation types -- referred to as meta-annotations -- to annotate your annotations. I'll cover each one in turn.

Specifying the target

The most obvious meta-annotation is one that allows you to indicate which program elements can have annotations of the defined type. Unsurprisingly, this meta-annotation is called Target . Before you see how to use Target , though, you need to know about another new class -- actually an enum -- called ElementType . This enum defines the various program elements that an annotation type can target. Listing 9 show the ElementType enum in its entirety:


Listing 9. The ElementType enum

package java.lang.annotation;

public enum ElementType {
TYPE, // Class, interface, or enum (but not annotation)
FIELD, // Field (including enumerated values)
METHOD, // Method (does not include constructors)
PARAMETER, // Method parameter
CONSTRUCTOR, // Constructor
LOCAL_VARIABLE, // Local variable or catch clause
ANNOTATION_TYPE, // Annotation Types (meta-annotations)
PACKAGE // Java package
}

 

The enumerated values in Listing 9 are pretty obvious, and you can figure out on your own (with help from the comments) how each one applies. When you use the Target meta-annotation, you supply it at least one of these enumerated values and indicate which program elements the annotated annotation can target. Listing 10 shows Target in action:


Listing 10. Using the Target meta-annotation

package com.oreilly.tiger.ch06;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
* Annotation type to indicate a task still needs to be completed
*/
@Target({ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE})
public @interface TODO {
String value();
}

 

Now the Java compiler will apply TODO only to types, methods, constructors, and other annotation types. This helps you ensure that nobody else takes your annotation type and misapplies it (or, better yet, that you don't misapply it in a fit of fatigue).

Setting retention

The next meta-annotation you want to get under your fingers is Retention . This meta-annotation is related to how the Java compiler treats the annotated annotation type. The compiler has several options:

  • Retain the annotation in the compiled class file of the annotated class, and read it when the class first loads
  • Retain the annotation in the compiled class file, but ignore it at runtime
  • Use the annotation as indicated, but then discard it in the compiled class file

These three options are represented in the java.lang.annotation.RetentionPolicy enum, shown in Listing 11:


Listing 11. The RetentionPolicy enum

package java.lang.annotation;

public enum RetentionPolicy {
SOURCE, // Annotation is discarded by the compiler
CLASS, // Annotation is stored in the class file, but ignored by the VM
RUNTIME // Annotation is stored in the class file and read by the VM
}

 

As you should expect by now, the Retention meta-annotation type takes as its single argument one of the enumerated values you see in Listing 11. You target this meta-annotation to your annotations, as shown in Listing 12:


Listing 12. Using the Retention meta-annotation

@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
// annotation type body
}

 

As you can tell from Listing 12, you can use the shorthand form here, because Retention has a single-member variable. And if you want the retention to be RetentionPolicy.CLASS , you don't have to do a thing, because that's the default behavior.

Adding public documentation

The next meta-annotation is Documented . This is another one that's pretty easy to understand, partly because Documented is a marker annotation. As you should remember from Part 1, marker annotations have no member variables. Documented indicates that an annotation should appear in the Javadoc for a class. By default, annotations are not included in Javadoc -- a fact worth remembering when you spend a lot of time annotating a class, detailing what's left to do, what it does correctly, and otherwise documenting its behavior.

Listing 13 shows what the Documented meta-annotation looks like in use:


Listing 13. Using the Documented meta-annotation

package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }

 

The one "gotcha" with Documented is in the retention policy. Notice that Listing 13 specifies the annotation's retention as RUNTIME . This is a required aspect of using the Documented annotation type. Javadoc loads its information from class files (not source files), using a virtual machine. The only way to ensure that this VM gets the information for producing Javadoc from these class files is to specify the retention of RetentionPolicy.RUNTIME . As a result, the annotation is kept in the compiled class file and is loaded by the VM; Javadoc then picks it up and adds it to the class's HTML documentation.

Setting up inheritance

The final meta-annotation, Inherited , is probably the most complicated to demonstrate, the least-often used, and the one that creates the most confusion. All that said, let's cheerily run through it.

First, take a use case: Suppose that you mark a class as being in progress, through your own custom InProgress annotation. No problem, right? This will even show up in the Javadoc if you've correctly applied the Documented meta-annotation. Now, suppose you write a new class and extend the in-progress class. Easy enough, right? But remember that the superclass is in progress. If you use the subclass, and even look at its documentation, you get no indication that anything is incomplete. You would expect to see that the InProgress annotation is carried through to subclasses -- that it's inherited -- but it isn't. You must use the Inherited meta-annotation to specify the behavior you want, as shown in Listing 14:


Listing 14. Using the Inherited meta-annotation

package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }

 

With the addition of @Inherited , you'll see the InProgress annotation show up on subclasses of annotated classes. Of course, you don't really want this behavior on all your annotation types (that's why the default is not to inherit); for example, the TODO annotation wouldn't (and shouldn't) be propagated. Still, for the case I've shown here, Inherited can be quite helpful.


Conclusion

At this point, you're ready to go back into the Java world and document and annotate everything. Then again, this reminds me a bit of what happened when everyone figured out Javadoc. We all went into the mode of over-documenting everything, before someone realized that Javadoc is best used for clarification of confusing classes or methods. Nobody looks at those easy-to-understand getXXX() and setXXX() methods you worked so hard to Javadoc.

The same trend will probably occur with annotations, albeit to a lesser degree. It's a great idea to use the standard annotation types often, and even heavily. Every Java 5 compiler will support them, and their behavior is well-understood. However, as you get into custom annotations and meta-annotations, it becomes harder to ensure that the types you work so hard to create have any meaning outside of your own development context. So be measured. Use annotations when it makes sense to, but don't get ridiculous. However you use it, an annotation facility is nice to have and can really help out in your development process.

 

Resources

  • Don't miss Part 1 of this series, which introduces annotations in Java 5.0.
  • The open source XDoclet code-generation engine enables attribute-oriented programming for the Java language.
  • JSR 175 , the specification for incorporating a metadata facility into the Java language, is in proposed final draft status under the Java Community Process.
  • Visit Sun's home base for all things J2SE 5.0. .
  • You can download Tiger and try it out for yourself.
  • John Zukowski's series Taming Tiger covers the new features of Java 5.0 in practical tip-based format.
  • Java 1.5 Tiger: A Developer's Notebook (O'Reilly & Associates; 2004), by Brett McLaughlin and David Flanagan, covers almost all of Tiger's newest features -- including annotations -- in a code-centric, developer-friendly format.
  • Find hundreds more Java technology resources on the developerWorks Java technology zone .
  • Browse for books on these and other technical topics.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值