Close Window

Print Story

Java Annotation Facility - A Primer

The 5.0 release of JDK introduced a slew of new features. A powerful technique that resulted from the JSR-175 recommendation is the Program Annotation Facility. It can annotate code in a standard way and automate the generation of source code or configuration files, helping cut down on boilerplate code.

At the moment, the closest thing to annotating source and generating support file/code is through java doc tags. The popular ones are @deprecated, @author, @param etc. However, these tags are pretty static by nature and the information they define isn't encoded in the class file by the compiler so it's not available at runtime. A popular implementation of this concept is XDoclet. This is an Open Source utility that lets a developer add metadata or attributes to source as java doc tags. Appropriate source files or configurations, such as deployment descriptors, are generated later using the ANT task provided by XDoclet. (The source code can be downloaded from www.sys-con.com/java/sourcec.cfm.)

The core Java language has always had some form of ad hoc annotation scheme. Java doc tags are an example. Another example is the keyword transient, which is used to mark a member variable so it can be ignored by the serialization subsystem.

All this changed with the introduction of JDK 5.0, which adds a general-purpose customizable annotation mechanism. This facility consists of syntax for declaring annotation types, syntax for annotating declarations, APIs for reading annotations, a class file representation for annotations and an annotation-processing tool.

Annotation and Annotation Types
The first step in the process is defining an annotation type. This is pretty simple to do and looks familiar as well. An annotation-type declaration looks like an interface declaration except an "@" symbol precedes the interface keyword. The method declaration that goes between the braces of this declaration defines the elements of the annotation type. Of course, since we are annotating the code and not defining behavior, logically speaking, these methods shouldn't throw any exception. That means no throws clause. Another restriction is that the return type for these methods is restricted to primitives: String, Class, enums, annotations and arrays of the preceding types. The complete lists of restrictions are as follows:

The following code snippet defines an annotation type for a servlet. Presumably, we could use this definition to annotate a servlet and then have an annotation tool generate web.xml. Here we define no args methods that define the various XML attributes/elements found in web.xml. For conciseness we have left out elements like init, load on startup, icon etc.


public @interface Servlet {
String servletName();
String servletClass();
String displayName();
String description();
}

Declaring Annotation
Now that we have the annotation-type defined we can annotate our servlet using the defined annotation type. Annotation is a new kind of modifier that contains an annotation type with zero or more member-value pairs. If a member has a default value defined in the annotation-type member declaration then the value can be omitted, otherwise, annotation must provide a member-value pair for all members defined in the annotation type. Annotation can be used for modifiers in any declaration - class, interface, constructor, method, field, enum, even local variable. It can also be used on a package declaration provided only one annotation is permitted for a given package. In our case we are annotating at the class level and the annotation precedes the access modifier public.


@Servlet(
servletName="AnnotatedServet",
servletClass="com.jdj.article.servlet.AnnotatedServet",
displayName="AnnotatedServet",
description="This is an example Annotated Servlet"
)
public class AnnotatedServet extends HttpServlet{...}

Now, to tie all these together we will look at how to build a simple annotation-driven framework. However, before we start looking at concrete code samples, we will go over a bit more of the theory behind this important addition to the core language API.

Meta Annotation Types
The API provides some annotation types out-of-the-box that can be used to annotate the annotation types. These standard annotation types are also known as meta-annotation types. The details are provided in Table 1.

Standard Annotation
The Tiger release of the JDK also bundles a set of standard annotation types. Table 2 defines the annotation tags and their purpose.

Annotation Retention
The consumers of annotation fall into three categories.

The grouping of annotation consu-mers mentioned above is determined by the retention policy that is specified by the RetentionPolicy enum present in the java.lang.annotation package. If the retention policy is 'CLASS' then the annota-tions are recorded in the class files but are not retained by the virtual machine. If the retention policy is 'RUNTIME' then the annotations are recorded in the class file and are retained by the VM at runtime. The value 'SOURCE' causes the compiler and VM to discard the annotation.

Annotation Processing Tool
The annotation processing tool (apt) found in JAVA_HOME/bin directory is a command-line utility that ships with JDK 5.0. This tool looks for annotation processors based on the annotation in the set of specified source files being examined. Essentially the annotation processor uses a set of reflective APIs and supporting infrastructure to process the annotations.

When invoked, the apt goes through the following sequence of operations: First, it determines what annotations are present in the source code being operated on. Next, it looks for annotation processor factories. It then asks the factories what annotations they process and, if the factory processes an annotation present in source files being operated on, the apt asks the factory to provide an annotation processor. Next, the annotation processors are run. If the processors have generated new source files, the apt will repeat this process until no new source files are generated. This high-level sequence is indicated in Figure 1.

To write a factory class, a developer has to rely on packages that aren't part of the standard SDK. The packages used are:

These packages are bundled in tools.jar, and so this jar file needs to be set in the classpath to write and compile the factory class. Assuming that the path and the classpath are set correctly, the annotation processing tool can be invoked from the command prompt by typing 'apt' followed by the tools command-line parameters.

Using Annotation for Generating Struts-config.xml
For a concrete understanding of the technology it's imperative that we dive into writing code and see for ourselves how it works. We will look at how to go about generating struts-config.xml by annotating code and using the apt tool. This article assumes that the reader is familiar with the Open Source MVC framework struts. We will look at how to generate the configuration details and the declarative programming semantics provided in the struts-config.xml by annotating the source code.

Before we get to the nitty-gritty of annotating and generating configuration files, we need to understand why this needs to be done.

We need to do this because a developer who has to write code using the struts framework finds himself copying and pasting information from the source code to the deployment descriptor and vice versa. For instance, if we change the name of the Action class and don't change the XML file, the application doesn't work correctly. The way to sidestep this issue is to isolate the changes to one location and let the utility tool generate the deployment descriptor. We will cover how to use the metadata facility to achieve this automatic configuration file generation.

Before we start we have to make sure that we have the right development tools to do what we're trying to do. Currently very few IDEs support Java 5. Among Open Source IDEs, NetBeans 4.0 beta 2 looks promising (in spite of a few runtime exceptions) so most of the code in this article was written and tested with it. The first step in the process is defining the annotation types for the various elements that make up the struts framework. The component parts of the framework are: Action, Form bean (also known as Action Forms), Exceptions, Validator, Plug-ins etc.

The annotation type for Struts Action is as follows:


package com.jdj.article.atypes;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface StrutsAction {
String name();
String path();
String scope() default "session";
String input() default "";
String roles() default "";
String validate() default "";
String parameter() default "";
StrutsActionForward[] forward();
StrutsActionException strutsAction
Exception();
}

As you can see, I have the annotation types as return types. This is necessary because we could have forward and exception elements defined by the action element. The XML snippet from that struts-config.xml that we're trying to generate will clarify the need for embedding other annotation types in the action annotation-type declaration.


<action path="/SubmitLogon"
type="example.LogonAction"
name="LogonForm"
scope="request"
input="logon">
<forward name="failure" path="/
MainMenu.do"/>
<forward name="success" path="/
someValidPage.jsp"/>
<exception key="expired.password"
type="example.Expired
PasswordException"
path="/ExpiredPassword.
do"/>
</action>

The next step is to annotate the code with the annotation type. In our case we will annotate a struts action class. This is done as follows:


package ....
import ....
@StrutsAction(
name=" example.LogonAction",
path="/SubmitLogon",
forward = {@StrutsActionForward(
name = "failure",
path = "/MainMenu.do" )},
strutsActionException =
@StrutsActionException(
key = "expired.password",
type = "example.ExpiredPassword
Exception",
path = "/ExpiredPassword.do"
)
)
public class ExampleAction extends Action {

/** Creates a new instance of ExampleAction */
public ExampleAction() {
}

public ActionForward execute(ActionMapping
mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
return null;
}
}

Before tackling how to generate code or other dependent files such as deployment descriptor, we should clarify a couple of aspects presented in the code snippet above. The StrutsAction annotation type defines many elements but only a few of them were actually declared in the real annotation. This is perfectly valid because the elements that don't have a value declared will take the default value defined in the annotation type. In this case most of the values defaulted to an empty string but the scope element has a default value of session. So if the developer doesn't explicitly specify the value, the default value will be used. In the StrutsAction annotation type, we also have the return type for the forward element as an array of StrutsActionForward elements. So annotation is done by separating the values by a comma wrapped in braces.


forward = {@StrutsActionForward( ... ),
@StrutsActionForward( ... ) },

In this code snippet two action forward element are specified and both of them are enclosed by a brace separated by a comma. Now that we've talked about annotation types and annotation, the core concepts of annotation have been pretty much covered.

Generating Code/Supporting Files
The final steps in the process are the procedures for generating code or support files like deployment descriptor. This could be the hardest part depending on the complexity of what we are trying to do. If we are generating source code based on annotation information then we need to have some kind of parser generator to generate the code. JavaCC hosted at www.java.net is an excellent parser generator implementation. Since we are generating an XML file, we used JDOM API to generate the deployment descriptor for struts.

There are two ways to do the code generation. The first is to write a driver code that uses the reflection API and the annotation API to figure out what annotations are present and then operate on them appropriately. The other is to use the annotation processing tool bundled with the JDK to process the annotation.

In the section above we explained how the annotation processing tool works. The annotation processor that's returned by the factory does the actual work of generating code. In the core processing we loop through the annotation types and have a visitor based on the Gang of Four's visitor design pattern do the work. This visitor processes the annotation on the class declaration, method declaration, package declarations etc. and does the file generation.

The code snippet for the factory, annotation processor and a simple visitor is as follows:


public class StrutsConfigGenerator implements
AnnotationProcessorFactory{
private static final Collection<String>
supportedAnnotations
= unmodifiableCollection(Arrays.asList("*"));
private static final Collection<String>
supportedOptions = emptySet();
public StrutsConfigGenerator() {}

public Collection<String> supported
AnnotationTypes() {
return supportedAnnotations;
}

public Collection<String> supported
Options() {
return supportedOptions;
}

public AnnotationProcessor get
ProcessorFor(
Set<AnnotationType
Declaration> atds,
AnnotationProcessor
Environment env) {
return (AnnotationProcessor)new
WebGeneratedAp(env);
}

private static class WebGeneratedAp
implements
AnnotationProcessor {
private final AnnotationProcessor
Environment env;
WebGeneratedAp(AnnotationProcessor
Environment env) {}

public void process() {
for (TypeDeclaration typeDecl :
env.getSpecifiedType
Declarations()){
// GoF visitor Design Pattern
applied here!
typeDecl.accept(get
DeclarationScanner(
new WebGeneratorClass
Visitor(), NO_OP));
}
}
private class WebGeneratorClassVisitor
xtends
SimpleDeclarationVisitor {
public WebGeneratorClassVisitor(){ }

public void visitClassDeclaration(
ClassDeclaration d)
{
/ /Do some meaningful work here!!!
}

// You could have more specific declarations here
}
}
}

An important thing that we need to take note of is the getProcessorFor method in the factory. This method returns the annotation processor and, in our case, is an instance of the WebGeneratedAp class. This class extends AnnotationProcessor and implements the no args process method. Here we loop through all the type declarations and then have the type declaration accept our visitor. Our visitor is WebGeneratorClassVisitor and has methods to handle the areas where annotations occur like class declaration, method declaration and package declaration. It's here that we read the annotation, find out what the value is and operate on it accordingly. The complete working copy of the source is provided along with this article.

The final step in the process is to invoke the Annotation Processing Tool. This is typically done through the command line as follows:


apt -cp [Classpath] -nocompile
-factory com.jdj.article.gen.StrutsConfigGenerator
-Astruts-config=F:\Article\Dev\Annotation\
generated\struts-config.xml <path>\
ExampleAction.java

In this method of invocation we used the -nocompile to ensure that the source file specified isn't complied. The -classpath option specifies the classpath required by the processor. The -A option is ignored by the APT tool and is used by the annotation processor to get any specific processing-related information. In our case, we specified the location of the struts-config.xml in the file system.

The last step is to process the files with the annotation processor. Besides the command line, one could write an ANT Custom Task to generate the code. We strongly recommend that this be done. Since more and more projects are using ANT for automating the build process, it makes more sense to do an ANT build for automating code generation as well.

Writing an ANT task is easy and done by writing a custom task and extending the org.apache.tools.ant.Task class. Once that's done, the next step is to define the attributes and the corresponding setter methods for the attributes. The core processing logic is provided in the execute method called by the ANT framework. In our case, we delegate the bulk of the code generation work to the com.sun.tools.apt.main.Main class from the tools.jar. This is the actual byte code that's also used by the apt command line.

The following XML snippet from an ANT build shows how use the custom ANT task:


<taskdef name="apt"
classname="com.article.jdj.
annotation.task.APTTask" >
<classpath>
<pathelement
location="F:\Article\Dev\Annotation\
dist\Annotation.jar"/>
</classpath>
</taskdef>
<target name="main">
<apt factory="com.jdj.article.gen.StrutsConfigGenerator"
src="F:\Article\Dev\Annotation\src\com\jdj\article\
action\*
.java"
processorOption="struts-config=
F: \Article\Dev\Annotation\generated\
struts-config.xml" >
<classpath>
<pathelement location="C:\Sun\AppServer\
lib\j2ee.jar"/>
<pathelement location="...">
</classpath>
</apt>
</target>

Summary
In this article we introduced the idea of defining annotation types, how to use defined types as annotation and how to go about generating code or other supporting files based on annotation. This new and powerful technology is transforming the way we code. As more and more tools and products start using this facility, developers will realize improved quality in their code and a concomitant increase in productivity.

Resources

  • JavaCC: https://javacc.dev.java.net/
  • JDoM: www.jdom.org/
  • APT: http://java.sun.com/j2se/1.5.0/docs/guide/apt/index.html
  • Annotations: http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
  • JSR 175: www.jcp.org/en/jsr/detail?id=175
  • © 2008 SYS-CON Media Inc.