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.
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 * *..*.*(..))
execution(@Asynchronous * *..*.*(..))
Read more about this in the Join point selection pattern language section
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() { ... }
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:
org.codehaus.aspectwerkz.annotation.TypedAnnotationProxy
class.
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.
All annotations are strongly typed. Both JavaDoc-style and Java 1.5 style annotations.
We currently support the following type of named parameters:
int=8366 dbl=86.2345D char='\n'
usecache=true failover=false
(
TRUE
and
FALSE
works a well)
name="blab\"labla"
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.
name=org.foo.Bar.PUBLIC_CONSTANT
type=java.lang.String.class
primitives={long.class, int.class, short.class, ...}
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.
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 */
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.
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>
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
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.
An Ant task is provided to compile the annotations.
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>
The AnnotationCTask task accepts the following:
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:
List annotationInfos = Annotations.getAnnotationInfos(klass); List annotationInfos = Annotations.getAnnotationInfos(method); List annotationInfos = Annotations.getAnnotationInfos(field);