Java 5 Annotations - Creating your own Annotation Type html

Java 5 Annotations – Creating your own Annotation Type

One of the new features in Java 5 that clearly stands out is called: Annotations (JSR-175). Through Annotations, we can add metadata to our Java sources, not just as custom tags in JavaDoc comments – as we used to do with simple annotations such as @author or @deprecated or more advanced annotations used by XDoclet or AspectJ – but as constructs that are supported by IDEs and checked by compilers. In a recent workshop in session of our Web & Java Knowledge Center, we implemented a very simple AnnotationType, called Descriptor Annotation, that had a direct impact on the toString() method. In a few simple steps, we define the Annotation Type, use it to annotate a class and write the runtime code to leverage the annotation, through Reflection. This article illustrates this process.


Annotations have a much longer history than Java 5. They have been abundantly used in C# to specify meta-data in code. In Java, tools like XDoclet, Apache Commons, MetaClass, qDox, JAM and others have used special tags inside JavaDoc comments as annotations. Runtime annotations could be achieved in somewhat farfetched way by using Marker Interfaces or even implementing dummy methods. In Java 5, such tricks are no longer necessary as Annotations are now part of the core Java Programming Language.

Given the history, it seems logical that the first serious usages of Annotations can be found in tools and frameworks that tried JavaDoc style tags before Java 5. Recent releases of AspectJ (5) and JUnit (4.0) as well as the Spring Framework, XFire, Tapesttry and many others are leveraging the Annotations concept. Additionally, some very important JEE 5 specifications involve or revolve around Annotations; most notably are JSR-220 EJB 3.0 Persistence and JSR-181 WebServices Meta-Data.

Some of the Java 5 Annotations objectives:

  • Declaratively specify meta-data (Without the need for external XML files)
  • Compile time verification – No typo’s, upper/lower case mismatch
  • Code completion in IDEs (Facilitate IDE support)
  • Provide meta-data access at Source file level/Compile time,Class Load Time, Run Time – extension of reflection API

There are a few things we can say in general about annotations and their appearance:

Annotations are included in the source code, just before the target they apply to. An annotation is included using the @ character, followed by the name of the Annotation Type. For example: @Entity or @Table(name=”MY_TABLE”). Between parentheses, we can supply values for parameters of our annotation. Note that the parameters can be of complex types. For example:

 

@ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(table = @Table(name = "als_authorships"), joinColumns = {
           @JoinColumn(name = "bok_id")
       }, inverseJoinColumns = {
           @JoinColumn(name = "atr_id")
       })
    public Collection getAuthors() {
        return this.authors;
    }

The AnnotationType must be imported, like a Class or Interface definition. Annotation Types must be on the project’s build path as well as – for runtime annotations – the classpath. An Annotation is part of the code, it is not in comments. Annotation is located just before its target. Potential targets are: Package, Class, Field, Constructor, Method, Parameter, Local Variable,
Annotation. Yes, an Annotation (type) can be annotated as well. We will see an example of such a meta-annotation in a short while.

Retention Policy

Annotations can live in different worlds. Part of the definition of the Annotation (Type) is an indication of its retention policy. Three levels can be set:

  • Source- Annotation is only in the source file. Can be used by a preprocessor at compile-time
  • Class (default) – Annotation is in the classfile, but cannot be accessed at runtime. Useful for postprocessors that work directly on the classfile
  • Runtime – Annotation is in the classfile and can be accessed via the introspection APIs

 

Creating our own Annotation Type

As developers, we will not create new annotation types all the time. Using the annotation types specified or required by frameworks like AspectJ 5, JUnit, EJB 3.0 Psersistence will be a much more frequent task. However, it is interesting to know how to create an annotation type, and you may find good uses for your own annotation types all the same.

In this example, we will create an Annotation called Descriptor. It can be used to mark the getter methods in our class that participate in completely describing instances of our class. For example the getters getId and getName are both annotated with the @Descriptor annotation in our Product Class, indicating that a description of Product objects that humans can understand can be constructed from those two setters. Or the class Person has getFirstName, getLastName and getBirthdate annotated with @Descriptor. We will later see how we can make use of these annotations at run time.

First our annotation with its attributes:

package nl.amis.annotation.descriptor;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Descriptor {
int sequence() default 1;
String label() default “”;
String value() default “”;
}
The attribute sequence specified the position of the getter-method within the display label. The label attribute is used to specify the prompt that can be included in the displaylabel.

Next we will use the Annotation in the Employee Class. We assume that FirstName, LastName and Job together and in this order provide a meaningful description for Employees. So we have annotated the associated getter-methods:

package nl.amis.hrm;

import nl.amis.annotation.descriptor.Descriptor;
import nl.amis.annotation.AnnotatedObject;

public class Employee {
private Double commission;
private Long empno;
private String firstName;
private String lastName;
private String job;
private Double salalary;

public Employee() {
}

public void setCommission(Double commission) {
this.commission = commission;
}

public Double getCommission() {
return commission;
}

public void setEmpno(Long empno) {
this.empno = empno;
}

public Long getEmpno() {
return empno;
}

@Descriptor(label=”First Name”,sequence=1)
public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getFirstName() {
return firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

@Descriptor(label=”Last Name”,sequence=2)
public String getLastName() {
return lastName;
}

public void setJob(String job) {
this.job = job;
}

@Descriptor(label=”Job”,sequence=3)
public String getJob() {
return job;
}

public void setSalalary(Double salalary) {
this.salalary = salalary;
}

public Double getSalalary() {
return salalary;
}
}
Now we have HrmManager cla
ss with a main method that i
nstantiates new Employee, assigns values through its setters and then displays its displaylabel to the console:

package nl.amis.hrm;

public class HrmManager {
public HrmManager() {
}

public static void main(String[] args) {
HrmManager hrmManager = new HrmManager();
Employee scott = new Employee();
scott.setFirstName(“Harry”);
scott.setLastName(“Scott”);
scott.setJob(“Developer”);
scott.setSalalary(new Double(“32421”));

System.out.println(scott);
}
}

If we run this manager, we see no meaningful output whatsoever. Have our @Descriptor annotations failed somehow?

nl.amis.hrm.Employee@4

The point is: we have not made use of the annotations anywhere. We have included them in the Employee class and since they were defined with Runtime Retention Policy they are available in the Employee Class at runtime, accessible through the Reflection API. But if we do not ask for it, they will not do anything by themselves.

In the class AnnotatedObject that extends Object, we have overridden the default toString() method. In our toString(), we get a list of all methods in the class, iterate over them and checks for each one of them if they have been annotated by @Descriptor. If so, the method is invoked and the result is added to the display label:

package nl.amis.annotation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
import nl.amis.annotation.descriptor.Descriptor;

public class AnnotatedObject {

public String toString() {
String tostring = “”;
Method[] methods = this.getClass().getDeclaredMethods();
SortedMap descriptors = new TreeMap();
for (Method m: methods) {
Descriptor annotation = m.getAnnotation(Descriptor.class);
if (annotation == null) {
continue;
}
Object[] args = null;
try {
descriptors.put(new Integer(annotation.sequence())
, annotation.label() + ” ” + m.invoke(this, args));
} catch (InvocationTargetException ite) {
} catch (IllegalAccessException iae) {
}
}
// if no annotated methods were found, fall back on the default toString implementation
if (descriptors.isEmpty()) {
tostring = super.toString();
} else {
Iterator descriptorIterator =
descriptors.values().iterator();
while (descriptorIterator.hasNext()) {
tostring = tostring + “;” + descriptorIterator.next();
}
}
return tostring.substring(1);
}

}

Now we have Employee extend AnnotatedObject:

package nl.amis.hrm;

import nl.amis.annotation.descriptor.Descriptor;
import nl.amis.annotation.AnnotatedObject;

public class Employee extends AnnotatedObject {
private Double commission;

And run HrmManager again:

First Name Harry;Last Name Scott;Job Developer

If we change the annotations in Employee, removing @Descriptor from getFirstName and adding it to getSalary, the outcome is as follows:

Last Name Scott;Job Developer;Salary 32421.0

Here we have seen a simple example of creating and using your own Annotation. It should help you create Annotations of your own.

Resources

Download the sources for the AnnotationsWorkshop: to be added later

2 Comments

  1. Peter Perhac February 25, 2011
  2. Prasanna February 24, 2011