Table of Contents

Annnotation driven development

Disclaimer: Java 1.5 annotation support is not implemented in the 1.0 release.

What is implemented is an API that will make the migration minimal and will allow both annotation schemes (JavaDoc-style and Java 1.5 annotations) to co-exist seamlessly. We also have support for typed and untyped annotations.

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

Matching on Annotations

AspectWerkz supports matching on annotations. This means that you can define your pointcuts to pick out join points that is 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

Typed Annotations

To be make the migration phase from JavaDoc-style to Java 1.5 annotations as smooth as possible AspectWerkz is using the concept of Annotation Proxies.

This proxy concept also makes it possible to achive strong typing even 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 proxy is a proxy class with getter setter methods for each key:value pair in the annotation.

For example if you have the JavaDoc annotation:

/**
 * @Asynchronous(useThreadPool=true timeout=60)
 * @Verbosity (level=2, prepend="LOG")
 * @VerbosityOther level=2 prepend="LOG\""
 */
 public Object someMethod() {
     ...
 }
                
Note that you can have one or more space between the annotation name and its initialization.

Note that annotation values can be separated by spaces or by comma within parenthesis, or just spaces and no parenthesis at all.

This can be written like this using Java 1.5 annotations

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

Then you can write an annotation proxy that works equally good with both of these schemes. Here is an example:

public class AsynchronousAnnotationProxy extends AnnotationProxyBase {
    private boolean m_useThreadPool;
    private int m_timeout;

    // Note:
    // the getter method can have the exact annotation value name (useThreadPool(), same case)
    // or follow the javabean convention with setUseThreadPool()
    public boolean useThreadPool() {
        return m_useThreadPool;
    }

    public int timeout() {
        return m_timeout;
    }

    // Note:
    // the setter method must follow the javabean convention though case can be ignored
    // like setuseThreadPool(..) or setUseThreadPool(..)
    public void setuseThreadPool(boolean flag) {
        m_useThreadPool = flag;
    }

    public void settimeout(int timeout) {
        m_timeout = timeout;
    }
}                

The key points in this example are:

  • The proxy extends the org.codehaus.aspectwerkz.annotation.TypedAnnotationProxy class.


  • There is a getter method with the exact same name as the name for the parameter (the name used in the annotation), or with a "get" prefix and following the javabean convention.


  • There is a setter method which has the name prefixed with set and can follow the javabean convention.


The getter methods will be used by you to retrieve the data and the setter methods are used by the framework to set the data in the proxy.

This proxy does now work equally good with JavaDoc style annotations and Java 1.5 style annotations.

Parameter types supported

All annotations are strongly typed. Both JavaDoc-style and Java 1.5 style annotations.

We currently support the following type of named parameters:

  • primitive values -

    int=8366 dbl=86.2345D char='\n'


  • boolean values -

    usecache=true failover=false ( TRUE and FALSE works a well)


  • strings (escape them as usual) -

    name="blab\"labla"


  • arrays -

    stringArr={"Hello", " ", "World", "!"}

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


  • references to the values of static fields -

    name=org.foo.Bar.PUBLIC_CONSTANT


  • types (ends with .class) -

    type=java.lang.String.class

    primitives={long.class, int.class, short.class, ...}


  • nested annotations -

    Not supported

Single anonymous typed value

You can also define just one single anonymous value for the annotation. This value will then be set by the framework through the method called void setValue(<type> value). So in order for this to work the parameter must be a supported type (see previous section). Add the method to the proxy class. This maps to the Java 1.5 simple value idea.

This behaviour is very similar to the untyped kind of annotations, but provides strong typing.

Untyped Annotations

For those who wants it AspectWerkz also supports old style, untyped JavaDoc annotations.

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

/**
 * @UntypedAnnotation 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, sine everything is treated as one single string.

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

UntypedAnnotationProxy proxy = ...
String value = proxy.value();
                 

The untyped annotations still needs to be compiled, since they need to be put into the bytecode of the class that declares them.

You can also extend the UntypeAnnotationProxy class to add additional behaviour, parsing the full value (like handling named parameters etc.). Then you need to override the setValue() method and add your logic there.

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 for custom annotations>]
                

The last option -custom property_file_for_custom_annotations points to the a property file which defines the annotations by mapping the names to the proxy implementations.

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 recommanded 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 proxy 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 proxy. That is handled by the compiler.

Example of an annotation properties file.

# Typed annotations
Requires      = test.expression.RequiresAnnotationProxy
Serializable  = test.expression.SerializableAnnotationProxy

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

For Java 1.5 annotations you would have to specify the fully qualified name of the annotation interface as the name.

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 <daskdef> 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 when user-defined annoations are to be used
  • classpath: classpath of classes to annotated, as well as classpath to discover user-defined annotations if any
  • classpathref: classpath reference of classes to 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 returns an instance of the type org.codehaus.aspectwerkz.annotation.Annotation. The proxies needs to be casted to the correct proxy implementation. 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 returns 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 returns a list with org.codehaus.aspectwerkz.annotation.AnnotationInfo instances which contains the:

  • name of the annotation
  • annotation proxy instance
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)