Taming Tiger, Part 3

Decorate your code with Java annotations

Welcome to the third and final part of this three-part series on Sun Microsystems' latest release of the Java 2 Platform, Standard Edition (J2SE). As I mentioned in Part 1, J2SE 5 (also called Tiger) is the most significant revision to the Java language since its original inception and has extended Java with several new features. To refresh your memory, Part 1 provided a brief introduction to J2SE 5 and covered many new additions to the Java language. Part 2 was devoted entirely to generics, which are Java's counterpart to templates in C++ and a similar facility (also called generics) in C# 2.0. In this final installment, I focus exclusively on the newly introduced metadata feature in J2SE 5 called annotations. Annotations allow programmers to decorate Java code with their own attributes. These attributes can be used for code documentation, code generation, and, even during runtime, for providing special services such as enhanced business-level security or special business logic.

Read the whole series: "Taming Tiger," Tarak Modi (JavaWorld):

What is metadata?

If you look up the definition of metadata, the dictionary will tell you it's data about data. The same definition applies to metadata in Java as well. Metadata is not a completely foreign concept to Java. Java has always supported a (very) limited implementation of metadata via its javadoc tags. An example code fragment follows:

/**
 * @author Tarak Modi
 *
*/
public final class AnnotationsTest
{
}

In the above code fragment, the @author tag is an example of metadata for the AnnotationsTest class. In other words, AnnotationsTest is annotated or decorated by the author metadata. Currently, this metadata is only used by tools such as javadoc and is not even available during runtime. XDoclet, an open source project, is another example that uses javadoc-style metadata tags to annotate and generate code based on the metadata.

With J2SE 5, the Java team has taken Java's metadata capability to a new level. The concept is formally called annotations. In addition to the built-in annotations included in Version 5, you can also use custom annotations that you define to decorate types (such as classes, interfaces, and enums), methods and constructors, and fields. You can control the availability of these annotations as well. For example, some annotations may be available only in the source code; others may be available in the compiled class files as well. Some annotations may even be available during runtime in the JVM.

Built-in annotations

J2SE 5 comes with six prebuilt annotations. Each annotation is described below. The annotation's actual definition is also provided in the reference's description. Two points to note from the definitions are their simplicity and that the annotations themselves are annotated. Later in this article, I walk you through a complete example of building and using your own annotation; during that discussion, I explain the declaration's syntax.

The following are the six built-in annotations in J2SE 5:

java.lang.Overrides

@Target(ElementType.METHOD)
public @interface Overrides 
{
}

This annotation is used to indicate that a method declaration in the current class is intended to override a method declaration in its (direct or indirect) superclass. If a method is decorated with this annotation type but does not override a superclass method, then the compiler will generate an error message. As an example, consider the following code fragment:

class A
{
@Overrides
    public String toString(int i)
    {
        return "";
    }
}

Compiling this code fragment produces the following error during compilation:

method does not override a method from its superclass
                @Overrides
                 ^

To fix the error, change the method signature from public String toString(int i) to public String toString(). Using this annotation is a good way to catch inadvertent programming errors during compilation, where you think you are overriding a method, but in reality you are creating a new one.

java.lang.annotation.Documented

@Documented
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented 
{
}

This annotation indicates that the annotation to which this is applied is to be documented by javadoc and similar tools. Note that this annotation is just a hint, and a tool can ignore the annotation if it desires to do so.

java.lang.annotation.Deprecated

@Documented
@Retention(RetentionPolicy.SOURCE)
public @interface Deprecated 
{
}

This annotation provides a hint to the Java compiler to warn users if they use the class, method, or field annotated with this annotation. Typically, programmers are discouraged from using a deprecated method (or class), because either it is dangerous or a better alternative exists.

As an example of when you could use the Deprecated annotation, consider a scenario where you have implemented a sort() method on a class used by many other developers on different projects. Later you discover a shortcoming in the code and not only have to rewrite the function, but also change its signature. You can't remove the old sort() method because other programs rely on it. What you can do is annotate it as being deprecated so when developers work on and compile their code, they will be advised to use the new sort() method instead of the old one.

java.lang.annotation.Inherited

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited 
{
}

The best way to understand this annotation is through an example. Let's say you created your own annotation called Serializable. A developer would use your annotation if his class implemented the Serializable interface. If the developer created any new classes that derived from his original class, then marking those classes as Serializable would make sense. To force that design principal, you decorate your Serializable annotation with the Inherited annotation. Stated more generally, if an annotation A is decorated with the Inherited annotation, then all subclasses of a class decorated by the annotation A will automatically inherit the annotation A as well.

java.lang.annotation.Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention 
{
    RetentionPolicy value();
}

The Retention annotation takes a single parameter that determines the decorated annotation's availability. The values of this parameter are:

  1. RetentionPolicy.SOURCE: The decorated annotation is available at the source code level only

  2. RetentionPolicy.CLASS: The decorated annotation is available in the source code and compiled class file, but is not loaded into the JVM at runtime

  3. RetentionPolicy.RUNTIME: The decorated annotation is available in the source code, the compiled class file, and is also loaded into the JVM at runtime

By default, all annotations are available at the source level only.

java.lang.annotation.Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target 
{
    ElementType[] value();
}

This annotation is used to indicate the type of program element (such as a class, method, or field) to which the declared annotation is applicable. If the Target annotation is not present on an annotation type declaration, then the declared type may be used on any program element. If the Target annotation is present, then the compiler will enforce the specified usage restriction. Legal values for the Target annotation are contained in the java.lang.annotation.ElementType enumeration. An annotation can be applied to any combination of a class, a method, a field, a package declaration, a constructor, a parameter, a local variable, and another annotation.

Create a custom annotation

It's easy to create and then use a custom annotation. As you have already seen above, the declaration of an annotation is straightforward. In this section, I walk you through a complete exercise of creating, applying, and dynamically querying a custom annotation during runtime.

The custom annotation

Let's create an annotation that can be applied to classes and methods and enforces role-based security. The declaration is shown below:

@Documented
@Target({METHOD,TYPE})
@Retention(RUNTIME) 
public @interface SecurityPermission
{
    String[] value();
}

We can glean the following information from the declaration:

  1. The annotation is to be documented by tools such as javadoc.
  2. The annotation can be applied only to methods and types such as classes.
  3. The annotation should be available during runtime.
  4. The annotation is called SecurityPermission.
  5. The annotation has one member called value and is an array of string. Note that we don't declare the actual member, only an accessor method to retrieve it.

The next step is declaring a class that uses our new annotation. An example is shown below:

@SecurityPermission("All")
public class AnnotationsTest
{
    public AnnotationsTest()
    {
        SecurityBlanket.checkPermission();
    }
    @SecurityPermission("All")
    public void unRestrictedMethod(String message)
    {
        SecurityBlanket.checkPermission();
        System.out.println("Message from unRestrictedMethod: " + message);
    }
    @SecurityPermission("None")
    public void fullyRestrictedMethod(String message)
    {
        SecurityBlanket.checkPermission();
        System.out.println("Message from fullyRestrictedMethod: " + message);
    }
    @SecurityPermission("Manager")
    public void partiallyRestrictedMethod(String message)
    {
        SecurityBlanket.checkPermission();
        System.out.println("Message from partiallyRestrictedMethod: " + message);
    }
    @SecurityPermission({"Manager","HR"})
    public void partiallyRestrictedMethod2(String message)
    {
        SecurityBlanket.checkPermission();
        System.out.println("Message from partiallyRestrictedMethod2: " + message);
    }
}

As you can see, AnnotationsTest is a class just like any other with special javadoc-like tags (@SecurityPermission) above the class and method declarations. This is how you apply the SecurityPermission annotation. Note that this is also the same way you apply the built-in annotations to Java code, such as, for example, to the SecurityPermission annotation declaration. Next, look at the various SecurityPermission annotation declarations to see how I specified the value parameter's value.

As you peruse through the AnnotationsTest class above, you may wonder what the call SecurityBlanket.checkPermission() is doing. This is where all the "magic" occurs. Simply declaring a custom annotation and then decorating your code with it is not going to accomplish much. The Java runtime has no idea what to do with your custom annotations. They just occupy valuable memory space doing nothing. That's where the SecurityBlanket.checkPermission() method call fits in.

First, this function figures out what the currently executing method is. Next, the function determines if the caller's role is one of the permitted roles for the method. If the method is a class constructor, then the function uses the roles specified in the SecurityPermission annotation applied to the class declaration; otherwise it uses the roles specified in the SecurityPermission annotation applied to the method declaration. The caller's roles are specified on a per-thread basis by calling the SecurityBlanket.addPermission method. In a real application, a custom JAAS (Java Authentication and Authorization Specification) module that allows a user to log into your application may call the SecurityBlanket.addPermission method on your behalf.

The point to realize from all this is that J2SE 5 provides us with a way of declaring our own custom annotations and a way of decorating our own code with them. It also loads them in the JVM and makes them available dynamically and reflectively. But then we have to write the glue logic that actually makes something useful happen.

The entire SecurityBlanket class is shown below:

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more