Section 21.5. J2SE Annotations Tutorial


21.5. J2SE Annotations Tutorial

J2SE 5.0 introduced a standard, generalized annotation model to the Java language itself. J2SE annotations are an actual part of the Java language syntax, with annotations inserted directly into your code (not just within comments, as you do with Javadoc tags). These annotations can be processed using a standard annotation processing tool provided with J2SE 5.0 environments, or you can write your own Java code to access and process annotations.

J2SE annotations differ from XDoclet annotations in two key ways. First, J2SE annotations are part of the actual language syntax. These annotations are supported directly by Java compilers, and new annotations are defined much like new classes and interfaces, using Java interface-like declarations.

The other difference is a temporary one. At this writing, the general infrastructure for defining and using J2SE annotations has been introduced in Java 5.0, and a few standard annotations are part of the core Java API. Annotations and processing tools based on J2SE annotations aimed at enterprise development (e.g., generating deployment descriptors or code artifacts for components) are still in development, as part of the next round of specifications in the web, EJB, and web services spaces, among others.

In the meantime, and even after these standard enterprise annotations are available, XDoclet and J2SE annotations are complementary tools that you can use together to ease the development and management of enterprise systems.

21.5.1. General Programming Model

Before we launch into some examples of J2SE annotations, you should note that the core annotations, and the support interfaces and enumerations we reference here, all reside in the java.lang and java.lang.annotation packages in the core Java API. We've omitted the imports of various java.lang.annotation elements in some of our examples for brevity.

J2SE annotations use the @ character to denote the start of code annotations, keeping with the convention set by Javadoc and XDoclet annotations. As with these other annotations, J2SE annotations can be used to assign metadata at various levels in your Java classes (class, method, and member variable). More formally, a J2SE annotation can be used anywhere that standard Java modifiers (like public, private, protected, or static) can be used.

Example 21-13 demonstrates the general syntax of J2SE annotations. The example shows a simple Java file with a class-level and method-level J2SE annotation. The name of the annotation immediately follows the @ character, and an annotation can optionally include a comma-delimited set of parameter values that are enclosed in parentheses immediately after the annotation name. These parameter values typically take the form of name=value, as in the Test annotation in the example. If the annotation accepts only a single parameter, as in the SuppressWarnings annotation, the parameter value can be given without providing the name. If an annotation takes no parameters, the annotation has no parameters or parentheses following it in the source, as in the Deprecated annotation.

Example 21-13. Sample code using J2SE annotations
 @SuppressWarnings("serial") public class Sample implements Serializable {     @Deprecated     public String doThis(String target) { return target + " done"; }       @Test(success="Success", failure="doThat call failed")     public int doThat(  ) { return this.hashCode(  ); } } 

In the following sections, we'll first look at the set of standard annotations that is built into the J2SE environment. Then we'll look at defining your own custom annotations.

21.5.2. Core Annotations

J2SE 5.0 provides a handful of standard annotations as part of the core Java API. These annotations are directly embedded in the Java environment. Currently, there are two categories of these core annotations. The first is compiler-related annotations. These annotations are general-purpose ones, used in source code to influence the checks performed at compile time. The @Override, @SuppressWarnings, and @Deprecated annotations fall into this category. We give a quick introduction to these annotations in the remainder of this section.

The other core annotations are used in the definition of other annotations, and they are sometimes called meta-annotations. The @Target, @Retention, @Inherited, and @Documented annotations fall into this category. We'll see the @Target and @Retention meta-annotations in action in the next section. To read about the @Inherited and @Documented annotations, see the more detailed coverage of annotations in Java in a Nutshell (O'Reilly).

21.5.2.1. @Override

This annotation is used to mark methods that are intended to override methods from their parent classes. You use this annotation to avoid the common error of intending to override a parent method but getting the signature slightly wrong. In most cases, the compiler will simply treat the method as an overloaded version of the parent's method and let it pass with no warning. The @Override annotation tells the compiler to fail outright if a method doesn't override a method in the parent class.

Here's a sample base class, Base, with a default implementation of a getIdentifier( ) method:

 public class Base {     /** Get the ID of this thing */     public String getIdentifier(  ) {         return this.getClass(  ).getName(  );     } } 

Now let's make a subclass of Base, named Child, with which we attempt to override the getIdentifier( ) method. Unfortunately, the signature is incorrect, and we've actually overloaded getIdentifier( ) rather than overridden it.

 public class Child extends Base {     /** Get the ID of this thing */     public String getIdentifier(String type) {         return type + ":" + this.getClass(  ).hashCode(  );     } } 

The Child class doesn't break any syntax rules, though, so the compiler will happily compile it with no errors. We can explicitly state that the getIdentifier( ) method in Child is meant to override a parent implementation by using the @Override annotation:

 public class Child extends Base {     /** Get the id of this thing */     @Override     public String getIdentifier(String type) {         return type + this.getClass(  ).hashCode(  );     } } 

If we compile this annotated code, the compiler will now generate an error:

 wunderkatzen[tcsh]> javac Base.java Child.java Child.java:3: method does not override a method from its superclass     @Override     ^ 1 error 

If you've ever struggled to find a subtle bug like this in a large collection of classes, the ability to annotate your overrides like this is a real boon. Now the compiler can warn you, for example, if you refactor your code and unintentionally break the inheritance chain.

21.5.2.2. @SuppressWarnings

The @SuppressWarnings annotation is used to filter out warnings that you do not want to see in the compiler output. Suppose, for example, we've implemented a Serializable data object in our system but would rather not specify a serial version ID, opting to allow the java.io runtime to generate a default one for us. Some compilers will complain about Serializable classes that do not provide their own serial version ID, but we can suppress these warnings using the @SuppressWarnings annotation:

 @SuppressWarnings("serial") public class MySerializable implements Serializable {     public MySerializable(  ) {         super(  );     } } 

Note that each compiler can choose its own naming convention for warning types for the @SuppressWarnings annotation. We assume here that the compiler is using "serial" to designate the serializable warnings we want to filter out, which is the designation used by the Sun J2SE 5.0 implementation. In addition, general support for the @SuppressWarnings annotation is optional as of this writing, so some Java 5.0 compilers may not even honor it. But support for it should firm up in future revisions of compilers, and the naming convention for compiler warnings should become more consistent as well.

21.5.2.3. @Deprecated

The @Deprecated annotation plays essentially the same role as the existing @Deprecated tag in Javadoc. You can use the @Deprecated annotation to mark classes and methods that may be removed in future versions of the code. The compiler will generate warnings when code uses these marked classes or methods to encourage developers to remove them from their code.

21.5.3. Creating and Using Custom Annotations

To demonstrate how to create and use your own J2SE annotations, we're going to use the context of a Java programming course. As the teacher of this course, you naturally create a number of code examples to demonstrate key principles discussed in class. You find yourself writing up detailed guides for the source code, highlighting key areas in the source code that participants should consult for examples relevant to various points discussed in class. Wouldn't it be nice, you think to yourself, if I could put these instructional highlights directly into the code and then generate the code guides directly from the annotated source code? J2SE annotations to the rescue.

Example 21-14 shows a definition of a simple annotation called @Learn. This annotation is a simple marker annotation because it takes no arguments. It simply marks the annotated item, similarly to the @Deprecated built-in annotation. Note that the syntax for defining an annotation is very similar to writing a Java interface. The annotation type uses the @interface modifier to distinguish it from a regular interface.

Example 21-14. Custom marker annotation
 import java.lang.annotation.*;   @Target({ ElementType.TYPE,           ElementType.METHOD,           ElementType.CONSTRUCTOR,           ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Learn {} 

In our example, we're making use of some of the built-in meta-annotations that are used to modify annotation definitions themselves. The first one in our example, @Target, is used to specify the code elements that are relevant for this annotation. Values from the ElementType enumeration are used to specify the allowable code locations. In our case, we want to use the @Learn annotation on classes and interfaces (TYPE), on methods (METHOD), on constructors (CONSTRUCTOR), and on member variables (FIELD). We could also specify LOCAL_VARIABLE for local variables in methods, PACKAGE for package-level annotations, and PARAMETER for annotations on method arguments. If there is no @Target annotation on an annotation definition, the annotation can be used anywhere. In our case, we're going to write a processor for these annotations and we don't want to insert annotations that won't be handled, so we explicitly name the cases we support with the @Target annotation. If we try to use our @Learn annotation on any code elements not specified in the @Target meta-annotation, we'll receive an error from the compiler.

The other meta-annotation we've used is @Retention. This annotation controls when and how we can access the annotation and its parameters, if any. This meta-annotation takes a single parameter, one of the three values in the RetentionPolicy enumeration: SOURCE, CLASS, or RUNTIME. The SOURCE value indicates that the annotation should be retained only in the source, so the compiler can discard it while processing the source code. The CLASS value is the default, and it indicates that the annotation should be retained in the compiled class file but not loaded into the Java runtime. The RUNTIME value indicates that we want the annotation to be available at runtime for processing. We're using the RUNTIME setting because we want to write our own Java program to process the @Learn annotations in our source.

Once we compile our annotation, we can use it in our source code to annotate elements that are interesting from an instructional standpoint, as in this example:

 // Imports omitted... @Learn public class CourseExample extends HttpServlet {     @Learn     private Context mCtx = null;       @Learn     protected CourseExample(  ) {         super(  );     }       @Learn     private String doSomethingInteresting(  ) {         . . .     }       private void thisMethodHasNoInstructionalValue(  ) {}       @Learn     public void doPost(HttpServletRequest request,                        HttpServletResponse response) {       . . .     } } 

Note that, if we define our custom annotation with a package, we need to import the annotation from that package in order to use it in source code. So in our example, if @Learn were defined in the package com.oreilly.jent.annotations, the CourseExample class would need to import it in the same way that external classes and interfaces need to be imported:

 import com.oreilly.jent.annotations.Learn; . . . 

This version of our @Learn annotation is a decent start, but we will quickly realize that we need more metadata in order to make this useful. First of all, we need to be able to put some descriptive text with the @Learn annotations to tell the students why this is valuable from an instructional perspective. We can extend our @Learn annotation to include a single text parameter, as shown in Example 21-15.

Example 21-15. @Learn annotation with a single parameter
 import java.lang.annotation.*;   @Target({ ElementType.TYPE,           ElementType.METHOD,           ElementType.CONSTRUCTOR,           ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Learn {     String value(  ); } 

Annotation parameters are defined using no-argument methods on the annotation interface, and the return type of the method indicates the type of the parameter. Annotation parameters are limited in terms of supported types. They can be primitive types (int, float, etc.), Strings, Classes, enumerated types, other annotations, or arrays of any of these.

Normally, you specify the value of an annotation parameter using the following syntax, providing the name of the parameter with its value:

 @Learn(value="You can learn a lot from this class (no pun intended)") public class Example { . . . } 

If an annotation has a single parameter named value, however, you can eliminate the parameter name from the annotation when you use it, like so:

 @Learn("You can learn a lot from this class (no pun intended)") public class Example { . . . } 

With this extended @Learn annotation, we can beef up our code markup quite a bit:

 // Imports omitted... @Learn("This servlet demonstrates the use of the XYZ design pattern.") public class CourseExample extends HttpServlet {     @Learn("As discussed in lesson 4, the use of the Context member" +            " is meant to simplify the rest of the servlet code")     private Context mCtx = null;       @Learn("This no-argument constructor will become interesting " +            "in module 3")     protected CourseExample(  ) {         super(  );     }       @Learn("Examine this method to see the \"keep it simple\" principles "           + "discussed in module 2.")     private String doSomethingInteresting(  ) {         . . .     }       private void thisMethodHasNoInstructionalValue(  ) {}       @Learn("Notice that we have a doPost(  ), but no doGet(  )")     public void doPost(HttpServletRequest request,                        HttpServletResponse response) {         . . .      } } 

We might even decide to extend our @Learn annotation even more, with another String parameter to indicate the relevant course module and a boolean flag to indicate whether the text of the annotation should be included only in the guide for its relevant module or in all module guides:

 import java.lang.annotation.*;   @Target({ ElementType.TYPE,           ElementType.METHOD,           ElementType.CONSTRUCTOR,           ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Learn {     String value(  );     String moduleId(  ) default "*";     boolean moduleOnly(  ) default false; } 

Notice that we've specified a default value for our moduleId and moduleOnly parameters, which allows us to leave those parameters unspecified when we use the annotation in code. This feature of annotations is useful in its own right, but in our example, it can help us avoid a lot of backward-compatibility issues with existing annotations in our code. Our existing @Learn annotations have only a single String parameter, and they will all still be valid with this new version of our @Learn annotation since the new parameters all have defaults. But, we can also make use of the new parameters where they are useful, like so:

 // Imports omitted... @Learn("This servlet demonstrates the use of the XYZ design pattern.") public class CourseExample extends HttpServlet {     @Learn(value="As discussed in lesson 4, the use of the Context member" +                  " is meant to simplify the rest of the servlet code",            module)     private Context mCtx = null;       @Learn(value="This no-argument constructor is used in our Cactus tests",            module,            moduleOnly=true)     protected CourseExample(  ) {         super(  );     }       @Learn(value="Examine this method to see the \"keep it simple\""                  + " principles discussed in module 2."            module)     private String doSomethingInteresting(  ) {         . . .     }       private void thisMethodHasNoInstructionalValue(  ) {}       @Learn("Notice that we have a doPost(  ), but no doGet(  )")     public void doPost(HttpServletRequest request,                        HttpServletResponse response) {         . . .     } } 

21.5.4. Processing Annotations at Runtime

Defining and using custom annotations is all well and good, but they're really interesting only when we process the annotations to do something useful. Any annotations that are available at runtime (i.e., in their definitions, they've been marked with an @Retention annotation with a value of RetentionPolicy.RUNTIME), you can access these annotations at runtime using the extensions to the Reflection API introduced in Java 5.0.

In our example, we want to process our @Learn annotations and generate HTML guides to the source code for the participants in our course. To accomplish this, we could write a simple Java class like LearnProcessor, shown in Example 21-16.

Example 21-16. Utility class for processing @Learn annotations
 // Imports omitted... public class LearnProcessor {     public static void main(String[] args) {         if (args.length < 2) {             System.err.println("USAGE: . . .");             System.exit(1);         }         String targetClassname = args[0];         try {             // Look up the class provided in the arguments             Class targetClass = Class.forName(targetClassname);             // Output the page header and start the table structure.             // If the class is annotated with a Learn tag, include the             // notation in the table as well.             if (targetClass.isAnnotationPresent(Learn.class)) {                 Learn annot =                     (Learn)targetClass.getAnnotation(Learn.class);                 outputHeader(targetClass, annot.value(  ));             }             else {                 outputHeader(targetClass, null);             }             // Check each member variable on the class, and output             // any notations found             Field[] fields = targetClass.getDeclaredFields(  );             for (int i = 0; i < fields.length; i++) {                 if (fields[i].isAnnotationPresent(Learn.class)) {                     Learn annot =                         (Learn)fields[i].getAnnotation(Learn.class);                     outputFieldNotation(fields[i], annot.value(  ));                 }             }             // Check each constructor on the class, and output a notation             // for any that have a Learn annotation             Constructor[] cons = targetClass.getConstructors(  );             for (int i = 0; i < cons.length; i++) {                 if (cons[i].isAnnotationPresent(Learn.class)) {                     Learn annot =                         (Learn)cons[i].getAnnotation(Learn.class);                     outputConstructorNotation(cons[i], annot.value(  ));                 }             }             // Check each method on the class, and output a notation             // for any methods that have a Learn annotation             Method[] methods = targetClass.getMethods(  );             for (int i = 0; i < methods.length; i++) {                 if (methods[i].isAnnotationPresent(Learn.class)) {                     Learn annot =                         (Learn)methods[i].getAnnotation(Learn.class);                     outputMethodNotation(methods[i], annot.value(  ));                 }             }             // Print the page footer             outputFooter(targetClass);         }         catch (ClassNotFoundException cnfe) {             System.err.println("Class \"" + targetClassname                                + "\" not found");         }     }     // Output methods omitted...     . . . } 

This is a simple utility class that's meant to be run from the command line, passing in the full name of the annotated class to be processed. We first attempt to load the class from the CLASSPATH using Class.forName( ) and then we use reflection to check for annotations on the class. First, we check for annotations at the class level itself, using the isAnnotationPresent( ) method, and if there is an @Learn annotation on the class, we call our outputHeader( ) method with the value parameter from the annotation:

     if (targetClass.isAnnotationPresent(Learn.class)) {         Learn annot =             (Learn)targetClass.getAnnotation(Learn.class);         outputHeader(targetClass, annot.value(  ));     } 

We then retrieve the fields declared on the class using the getFields( ) method and iterate over them, checking for @Learn annotations and calling the appropriate output method for each one found. We repeat the basic processing for the class constructors and methods.

We've omitted the details of the output methods here, but if we ran our LearnProcessor over the CourseExample class shown earlier, like so:

 > java LearnProcessor CourseExample 

it would not be hard to imagine generating HTML as shown in Figure 21-2.

Figure 21-2. HTML guide generated from @Learn annotations


21.5.5. The Future of J2SE Annotations

If you compare XDoclet annotations to J2SE annotations, you'll quickly note that XDoclet is largely a set of predefined annotations that are handy for enterprise development, while J2SE is a tool for creating annotations that doesn't (yet) include any predefined annotations for things like generating deployment descriptors or web service interfaces.

Fortunately, work is afoot to define standard annotations, based on the J2SE annotation syntax, for a variety of types of enterprise development. The JAX-RPC 2.0 specification (in draft at this moment) includes standard annotations that allow us to annotate JAX-RPC service implementation classes in much the same way that the XDoclet wsee tag group does. A JAX-RPC service implementation will be annotated something like this in JAX-RPC 2.0:

 @WebService public class PeopleFinderImpl {     . . .     @WebMethod     public Person[] findPeople(SearchArg[] args)         throws InvalidSearchException, PersistenceException {          . . .     } } 

Standard annotations are being defined in the EJB 3.0 specification (also in draft) that will allow you to indicate EJB types (stateful, stateless, entity), annotate business methods, specify persistence mappings, automatically generate remote and local interfaces, and more. See the end of Chapter 6 for a look at these annotations.

In the meantime, J2SE is useful in its own right as a general annotation facility, and XDoclet provides a wealth of enterprise-oriented annotations that you can use right now.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net