«    »

Working with Java 5 Annotations

Annotations are a new language feature introduced in Java 5 that allows Java code elements such as classes or methods to be annotated with structured metadata. This metadata can then be used at compile-time or at run-time by other code. Annotations are commonly used to provide configuration information for infrastructure frameworks to provide cross-cutting functionality.

Using existing annotations is fairly easy - an annotation is prefixed with '@' and optionally suffixed with one or more arguments of the form (<single-arg-value>) or (<arg1=value1>, <arg2=value2>, ...). The annotation is added immediately before the Java code element being annotated. Below is an example using two of the standard annotations defined in Java 5 (@Override and @SuppressWarnings):

package com.basilv.examples.annotations;

import java.util.ArrayList;

@SuppressWarnings("unchecked") 
public class AnnotationUsage
{
  public void doStuff() {
    ArrayList list = new ArrayList();
    list.add("");
  }

  @Override 
  public String toString() {
    return "AnnotationUsage";
  }
}

Defining a new annotation is a little more complicated. While an annotation is conceptually fairly similar to a Java interface - it has a name and a set of properties - the syntax for defining an annotation is, unfortunately, somewhat different from the syntax used to define an interface. This is best explained with an example:

package com.basilv.examples.annotations;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) 
@Target( {ElementType.TYPE, ElementType.METHOD }) 
public @interface MyAnnotation {
  int myProperty();
  String myPropertyWithDefault() default "";
}

The @interface syntax is bizarre - I mentally convert it to annotation - i.e. public annotation MyAnnotation. (I am not sure why Sun did not go with this more intuitive syntax, but I assume they did not want to create another reserved keyword for compatibility reasons - a weak reason considering that they added the assert keyword in v1.4 and the enum keyword in v5.) The declaration of individual properties is similar to the declaration of methods in interfaces except for the optional default clause that can be supplied. If no default is supplied then a value for the property must be supplied when using the annotation.

Annotations with no properties are allowed and are called marker annotations. Annotations with a single property by convention name the property value(), which allows the annotation property value to be declared without the name of the property: i.e. @Annotation("value").

The other interesting quirk is that Java annotations (@Retention and @Target) are used when declaring an annotation. The @Retention annotation defines how the metadata for uses of this annotation is retained via the RetentionPolicy enum. The default is RetentionPolicy.CLASS, which stores the metadata in the compiled class file but does not necessarily load it at runtime. I think the default should have been RetentionPolicy.RUNTIME, which ensures that the Java VM will load the metadata and make it available at runtime. When coding my first annotation I forgot to specify the runtime retention policy. This leads to the puzzling error of not being able to retrieve at runtime the metadata on the classes using the annotation, with no indication of what was wrong. The @Target annotation specifies the Java code elements that this annotation can be applied to.

Annotation metadata is only useful if we can retrieve and act on it. The Reflections API in Java 5 was augmented with methods to retrieve annotation metadata for java code elements such as classes and methods. These annotation related methods are defined by a common interface java.lang.reflect.AnnotatedElement. Examples include boolean isAnnotationPresent(Class) and T getAnnotation(Class).

The following example shows a somewhat-realistic example of an annotation being used. I define an annotation to map URLs to methods on a class, and then implement a servlet to delegate requests to the appropriate method using this metadata. First the definition of the annotation:

package com.basilv.examples.annotations;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)// or source or compile
@Target( { ElementType.TYPE, ElementType.METHOD }) 
public @interface WebAction {
  String url();
}

Now the class which uses this annotation to map URLs to particular methods:

package com.basilv.examples.annotations;

import javax.servlet.http.*;

public class ActionExecutor
{
  private HttpServletRequest request;
  private HttpServletResponse response;

  public ActionExecutor(HttpServletRequest request,
    HttpServletResponse response) {
    this.request = request;
    this.response = response;
  }

  private void setMessage(String message) {
    request.setAttribute("message", message);
  }

  @WebAction(url = "/test") 
  public void doTest() {
    // Set a message for demo purposes. Normally execute some logic.
    setMessage("test");
  }

  @WebAction(url = "/submit") 
  public void submitWork() {
    // Set a message for demo purposes. Normally execute some logic.
    setMessage("submit");
  }
}

Now the servlet to handle requests by executing the appropriate method on ActionExecutor:

package com.basilv.examples.annotations;

import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.*;
import javax.servlet.http.*;

public class DispatchingServlet extends HttpServlet
{
  @Override 
  protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException,
    IOException {
    response.setContentType("text/html;charset=UTF-8");

    String url = request.getPathInfo();
    ActionExecutor executor = new ActionExecutor(request,
      response);

    boolean actionFound = false;
    for (Method method : ActionExecutor.class.getMethods()) {
      if (method.isAnnotationPresent(WebAction.class)) {
        WebAction action = method.getAnnotation(
          WebAction.class);
        if (url.equals(action.url())) {
          actionFound = true;
          try {
            method.invoke(executor, (Object[]) null);
          } catch (Exception e) {
            throw new RuntimeException(
              "Error invoking method " + method.getName()
                + ".", e);
          }
        }
      }
    }
    if (!actionFound) {
      request.setAttribute("message", "No action found");
    }
    RequestDispatcher rd = request
      .getRequestDispatcher("/view.jsp");
    rd.forward(request, response);
  }

  @Override 
  protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException,
    IOException {
    doGet(request, response);
  }
}

There are a number of scenarios for which it would be useful to iterate over all classes with a particular annotation. The above example of the dispatching servlet would be more useful if it could dispatch to methods on any number of classes based on a class-level annotation. The Reflections API, however, does not provide a mechanism for obtaining all classes with a particular annotation. This is difficult functionality to provide for a number of reasons explained by Bill Burke, but since it is so valuable various open source projects such as Spring and JBoss have implemented solutions to this.

The source code listed in this article is provided in the Java Examples project which can be downloaded from the Software page.

If you find this article helpful, please make a donation.

3 Comments on “Working with Java 5 Annotations”

  1. tamgo says:

    thanks for writing this article, have spent hours looking for how to extract properties from annotations. This was the first thing that demonstrated how to do it.

  2. Cor says:

    What I am curious about is the following:
    There is a mechanism to override the annotated values, in the J2EE world the annotated values are overridden by the XMLs at runtime, presumably to prevent inserting production values in a development environment -which may not be known at development time.
    But which ever way I try, I cannot come up with a mechanism which allows for an override of the values while working with the annotations as an interface. I have not been able to find a setting mechanism which allows me to override a value with another (which might be retrieved from file).

    Likewise I have not been able to find a mechanism to add an annotation at runtime. This then leaves me to write implementations for the interfaces (which are no real interfaces) representing the annotations. That however is disencouraged by my compiler/IDE telling me that annotations should not be used as an interface.
    As a consequence I would be forced to write another interface or container class receiving the information from the annotations and/or the overriding file, but in my view that should be not necessary as the interface is already present (i.e. the annotation). And defining a interface extending the annotation interface is disencouraged as well. So I am almost forced to duplicate the information from the annotation interface, which is against all views regarding re-use in OO.

    What is your view/advice on this?

  3. I’m not sure I follow what you are trying to accomplish. With respect to overriding annotations with XML in Java EE, this is done by a container (Spring context, Hibernate session factory, EJB container, etc.) that populates an internal configuration/metadata structure based on defaults, then revises it based on information provided in annotations and separate XML configuration files. The key is the central container to manager this process. While I’m not sure exactly how you are trying to override annotations, using the same approach of a container responsible for populating an internal data structure is a good pattern to follow.

Leave a Reply

(Not displayed)

«    »