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.
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.
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 * *..*.*(..))
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.
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(); }
@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:
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:
@Annotation(integ=8366, dbl=86.2345D, achar='\n')
@Annotation(usecache=true, failover=false)
@Annotation(name="blab\"labla")
@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.
@Annotation(name=org.foo.Bar.PUBLIC_CONSTANT)
@Annotation(type=java.lang.String.class)
@Annotation(primitives={long.class, int.class, short.class, ...})
@ComplexNested(nesteds={@Simple(val="foo"), @Simple(val="bar")})
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() { ... }
// 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.
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 */
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 * ") */
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>
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
Requires
is the typed
@Requires
annotation
loggable
is the untyped
@loggable
annotation
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 <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>
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 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:
List annotationInfos = Annotations.getAnnotationInfos(klass); List annotationInfos = Annotations.getAnnotationInfos(method); List annotationInfos = Annotations.getAnnotationInfos(field);