Table of Contents

Annnotation driven development

AspectWerkz fully supports Java 5 annotations to define aspects or simply match on, and provides a Java 1.3/1.4 annotation API and custom compiler that allows to use the same feature with a very close user experience. Strongly typed Java 1.3/1.4 annotations facilities is thus provided.

  1. Annotations
  2. Matching on Annotations
  3. Typed Annotations
  4. Untyped Annotations
  5. Compiling Annotations
  6. Ant task for compiling Annotations
  7. Runtime retrieval of Annotations
  8. Introducing Annotations

Annotations

Java 5 Annotations are standardized thru the JSR-175. When you compile annotated source code the javac compiler will embed the annotations in the compiled class, and this information can then be read back using the reflection API.

Annotations in Java 5 are first class citizen thru the @interface keyword.

// A Java 5 Annotation
public @interface Asynchronous {
    int timeout() default 0;
    String label();
}

// A Java 5 annotated method
@Asynchronous(timeout=5, label="will run for a while")
public Object someMethod() {
    ...
}
                

AspectWerkz Java 1.3/1.4 Annotations provide the same user experience. Annotations are defined using a regular Java interface (hence providing strongly typed access to annotation values), and annotated source code appears in the form of doclet in the JavaDoc part.

The AspectWerkz AnnotationC compiler (available thru command line or as an Ant task) allows you to post-compile your classes to embed the annotation information. This extra compilation step is made useless when using Java 5 Annotations.

// A Java 1.3/1.4 Annotation with AspectWerkz
public interface Asynchronous {
    int timeout();
    String label();
}

// A Java 1.3/1.4 annotated method
// the resulting class will have to be post compiled 
// with AspectWerkz "AnnotationC -src *.java. -target compiled/"
/**
 * @Asynchronous(timeout=5, label="will run for a while")
 */
public Object someMethod() {
    ...
}
                

The AspectWerkz org.codehaus.aspectwerkz.Annotations API allows to retrieve reflectively both Java 5 annotations and Java 1.3/1.4 annotations in a consistent way, allowing you to adopt annotation driven development even without Java 5, while having a simple migration path.

Matching on Annotations

AspectWerkz supports matching on annotations. This means that you can define your pointcuts to pick out join points that are annotated with a certain annotation.

The matching will work equally good with JavaDoc-style annotations or a Java 1.5 annotations.

For example if you have annotated a method using the @Asynchronous annotation:

@Asynchronous(timeout=60)
public Object someMethod() {
    ...
}
                

You can then pick out this method (and all other methods that are annotated with the @Asynchronous annotation like this:

call(@Asynchronous * *..*.*(..))
                
or like this
execution(@Asynchronous * *..*.*(..))
                

Read more about this in the Join point selection pattern language section

Currently it is not possible to match on annotation element values.

Typed Annotations

Java 5 annotations are typed. To provide the same user experience in Java 1.3/1.4 and reduce the migration cost, AspectWerkz is using the concept of Annotation interfaces.

This concept makes it possible to achieve strong typing for JavaDoc-style annotations. Errors are reported already in the compilation phase. Which has many benefits compared to a the weakly typed, string based solution.

An annotation interface for Java 1.3/1.4 is a regular interface with one no-argument method per element in the annotation, whose returned value is of the wished type. The concept of defaults value that exists in Java 5 is not supported, and the default value will thus be null (or 0 / false for primitives).

For example if you have the JavaDoc annotation:

// Java 1.3/1.4 Annotations
/**
 * @Asynchronous(useThreadPool=true, timeout=60)
 * @Verbosity(level=2, prepend="LOG")
 * @VerbosityOther(level=2, prepend="LOG\"")
 */
 public Object someMethod() {
     ...
 }
                

This can be written like this using Java 1.5 annotations (mainly remove the JavaDoc comments and you are done)

@Asynchronous(useThreadPool=true, timeout=60)
@Verbosity(level=2, prepend="LOG")
@VerbosityOther(level=2 prepend="LOG\"")
public Object someMethod() {
    ...
}
                

When using Java 5, you have already written (or are using) the @Asynchronous annotation @interface component. For Java 1.3/1.4, you need to write (adapt this component). Here is an example with Java 5

// do not use a SOURCE retention policy if you plan to match on this annotation
// refer to Java 5 documentation about retention policy
public @interface Asynchronous {

    // use the exact annotation element name for the mehod name
    public boolean useThreadPool();

    public int timeout();
}
                
And the same example using AspectWerkz Annotation interfaces (mainly remove the @ sign in @interface and remove Java 5 specific annotations for @Retention and @Target, and remove defaults directives):
// retention policy will always be equivalent to Java 5 runtime retention policy
public interface Asynchronous {

    // use the exact annotation element name for the mehod name
    public boolean useThreadPool();

    public int timeout();
}                

The key points in this example are:

  • The annotation interface for Java 1.3/1.4 is just an interface. For Java 5 it is a regular @interface.


  • There is one method per annotation element whose name match exactly the element name as to be used in the annotated source code.


  • Java 1.3/1.4 annotation will appear in the javadoc part of annotated elements. The retention will be runtime, accessible thru AspectWerkz Annotations API, and annotated sources will have to be post-compiled using AspectWerkz AnnotationC

Parameter types supported

All annotations are strongly typed. Both JavaDoc-style and Java 1.5 style annotations. Java 5 annotations implies some limitations, and the Java 1.3/1.4 annotations follow the same rules.

We currently support the following type of named parameters:

  • primitive values -

    @Annotation(integ=8366, dbl=86.2345D, achar='\n')


  • boolean values -

    @Annotation(usecache=true, failover=false)


  • strings (escape them as usual) -

    @Annotation(name="blab\"labla")


  • arrays (limited to one dimension as per current Java 5 specification) -

    @Annotation(stringArr={"Hello", " ", "World", "!"})

    @Annotation(floatArr={46.34F, 836.45F}). Note that all elements of the array must be typed accordingly - if a float is expected, the F suffix is mandatory.


  • references to the values of static fields -

    @Annotation(name=org.foo.Bar.PUBLIC_CONSTANT)


  • types (ends with .class) -

    @Annotation(type=java.lang.String.class)

    @Annotation(primitives={long.class, int.class, short.class, ...})


  • nested annotations -

    @ComplexNested(nesteds={@Simple(val="foo"), @Simple(val="bar")})

Anonymous typed value

You can also define just one single anonymous value for the annotation. This value will then be accessible thru a method whose name is value(), exactly as Java 5 defines it.

When using such an annotation, it is thus optional to name the element "value" when annotating an application:

    // A Java 5 Annotation with an anonymous element
    public @interface Asynchronous {
        int value() default 0;
        String label() default "";
    }

    // A Java 5 annotated method using the anonymous element
    // Note: we could write @Asynchronous(value=5) but this is useless
    // except when using label as in @Asynchronous(label="some text", value=5)
    @Asynchronous(5)
    public Object someMethod() {
        ...
    }
                    
And for Java 1.3/1.4 AspectWerkz annotation interface:
    // A Java 1.3/1.4 Annotation with an anonymous element
    public interface Asynchronous {
        int value();
        String label();
    }

    // A Java 1.3/1.4 annotated method using the anonymous element
    // Note: we could write @Asynchronous(value=5) but this is useless
    // except when using label as in @Asynchronous(label="some text", value=5)
    /**
     * @Asynchronous(5)
     */
    public Object someMethod() {
        ...
    }
                    

In this last case, it is also possible to write it in a more JavaDoc oriented way, although it is a bad practice since it will make your code harder to migrate to Java 5.

    /**
     * @Asynchronous 5
     */
    public Object someMethod() {
        ...
    }
                    

Note that anonymous element in such an annotation is still strongly typed, and this last syntax may lead to confusion with the Untyped Annotation discussed below hence is not encouraged.

Untyped Annotations

For those who wants it AspectWerkz also supports old style, untyped JavaDoc annotations. In such a case you don't have to write an annotation interface since a standard one is already provided.

It treats everything after the annotation declaration as one single value of type String. Which means that if you write an annotation like this:

/**
 * @SampleUntypedAnnotation this (is
 *                    one single
 *                    value
 */
                
the value of this annotation will be: this (is one single value and the type will be java.lang.String. If you have key:valule pairs then you will have to parse them yourself, since everything is treated as one single string.

All untyped annotations will be wrapped in an instance of org.codehaus.aspectwerkz.annotation.UntypedAnnotation which has to be used when retrieving the annotations at runtime. For example:

UntypedAnnotation annotation = ... // see next sections
String value = annotation.value(); //"this (is one single value" - note that cariage returns are lost
String name = annotation.name(); // "SampleUntypedAnnotation"
                 

The untyped annotations still needs to be compiled, since they need to be put into the bytecode of the annotated classes.

Such an annotation can also be written this way (as an anonymous one whose value() element is of type String)

/**
 * @SampleUntypedAnnotation("this (is
 *                    one single
 *                    value
 * ")
 */
                

Compiling Annotations

If you are using custom JavaDoc-style annotations then you have to compile in into bytecode of the classes. This is done with the AnnotationC compiler.

Please note that this is not needed for Java 1.5 annotations.

You can run AnnotationC from the command line. (It might be useful to run the ASPECTWERKZ_HOME/bin/setEnv.{bat|sh} script first.)

You invoke the compiler like this:

java [options...] org.codehaus.aspectwerkz.annotation.AnnotationC
    [-verbose]
    -src <path to src dir>
    -classes <path to classes dir>
    [-dest <path to destination dir>]
    [-custom <property file(s) for custom annotations>]
                

The last option -custom property_file(s)_for_custom_annotations points to the (or several files separated by classpath separator - ; or : depending on you OS) property file which defines the annotations by mapping the names to the fully qualified names of the annotation interface.

Note that if you are using the -dest option, the anonymous inner classes will not be copied to the destination directory, since the anonymous classes are not taken into account by the Annotation compiler. In such a case it is recommended to add the following (if using Ant) just after the call to AnnotationC when the -dest option is used: (adapt according to the directories you are using)

<copy todir="classes/annotated" overwrite="false"> <fileset dir="classes/regular"/> </copy>

Annotation definition file

You need to tell the annotation compiler which annotations you are interested in and map the name of the annotations to the annotation interface implementation.

For untyped annotations you still need to define the name of the annotation but but you can leave out the mapping to a specific interface. That is handled by the compiler and will implicitly be org.codehaus.aspectwerkz.annotation.UntypedAnnotation.

Example of an annotation properties file.

# Typed annotations
Requires      = test.expression.RequiresAnnotation
Serializable  = test.expression.SerializableAnnotation

# Untyped annotations
loggable
readonly
                    
In which for example:
  • Requires is the typed @Requires annotation
  • loggable is the untyped @loggable annotation

Ant task for compiling Annotations

An Ant task is provided to compile the annotations.

Usage

First you need to activate the custom task in your Ant build.xml file with the following: (refer to Ant documentation on "taskdef" for more details)

<!-- we assume we defined a classpath with the id="aw.class.path" for AspectWerkz jars -->
<path id="aw.class.path">
    ...
    <pathelement path="pathToAspectWerkz.jar"/>
    ...
</path>

<!-- define the custom task (annotationc can be changed to what you prefer)
<taskdef name="annotationc" 
         classname="org.codehaus.aspectwerkz.annotation.AnnotationCTask" 
         classpathref="aw.class.path"/>
<!-- Note: the <taskdef> element can be nested within a <target> element at your convenience -->

<!-- invoke the annotationc defined task -->
<target name="samples:task:annotationc" depends="init, compile:all">
    <annotationc
        verbose="true"
        destdir="${basedir}/target/samples-classes"
        properties="${basedir}/src/samples/annotation.properties"
        copytodest="**/*.dtd">

        <src path="${basedir}/src/samples"/>
        <src path="${basedir}/src/test"/>
        <classpath path="${basedir}/target/samples-classes"/>
        <classpath path="${basedir}/target/test-classes"/>
        <classpath path="${basedir}/target/classes"/>
        <fileset dir="other">
            <include name="**/BAZ.java"/>
        </fileset>
    </annotationc>
</target>
                 

Reference

The AnnotationCTask task accepts the following:

  • verbose: [optional] flag marking the task verbosity [true / false]
  • properties: [optional] path to a properties file when user-defined annoations are to be used
  • destdir: [optional unless input classes are in more than one path] directory where to put annnotated class files
  • copytodest: [optional] filename pattern to copy extra resources like dtd, xml, or properties files that were found in the input classes path(s). By defaults, only ".class" files will be handled. It is ignored if "destdir" is not set.
Use the following parameters to configure the classpath to point to the classes to be weaved. Those can be specified with nested elements as well / instead:
  • properties path=..: extra path to a properties file where user-defined annotations are to be used
  • classpath: classpath of classes to be annotated, as well as classpath to discover user-defined annotations if any
  • classpathref: classpath reference of classes to be annotated, as well as classpath to discover user-defined annotations if any
  • srcdir: directory where to find annotated java source files
  • sourcepath: path where to find annotated java source files
  • sourcepathref: path reference where to find annotated java source files
Nested elements are similar to the "javac" task when you configure a classpath and a sourcepath:
  • classpath: Path-like structure of classes to annotated, as well as classpath to discover user-defined annotations if any
  • src: single path entry of annotated java source files
  • sourcepath: Path-like structure of annotated java source files
  • fileset: fileset to contain annotated java source files

Runtime retrieval of Annotations

You can retrieve the annotations at runtime using the Annotations class.

Here are some examples. The name in these examples is the annotation name for JavaDoc-style annotations and the fully qualified name of the annotation interface for Java 1.5 annotations.

All these methods return an instance of the type org.codehaus.aspectwerkz.annotation.Annotation. The instance needs to be casted to the correct annotation interface. If there are more than one it returns the first one found. This method is useful when working with Java 1.5 annotations in which there can be only one instance per member or class.

Annotation annotation = Annotations.getAnnotation("Session", klass);
Annotation annotation = Annotations.getAnnotation("Transaction", method);
Annotation annotation = Annotations.getAnnotation("ReadOnly", field);
                

All these methods return a list with all Annotation instances with the specific name. For Java 1.5 annotations this list will always be of size 0-1 while JavaDoc-style annotations can be declared multiple times per member/class.

List annotations = Annotations.getAnnotations("Session", klass);
List annotations = Annotations.getAnnotations("Transaction", method);
List annotations = Annotations.getAnnotations("ReadOnly", field);
                

These methods return a list with org.codehaus.aspectwerkz.annotation.AnnotationInfo instances which contains the:

  • name of the annotation
  • annotation instance (implements the annotation interface)
List annotationInfos = Annotations.getAnnotationInfos(klass);
List annotationInfos = Annotations.getAnnotationInfos(method);
List annotationInfos = Annotations.getAnnotationInfos(field);
                

Introducing Annotations

TODO - not yet implemented (targetted for 2.0 final)