What is important to understand is that both the Annotation and the XML definition are just different views of the same underlying model. This means that they can easily co-exist and can succesfully be used together. The XML definition can be used as a replacement to the Annotation definition or as a complement. It can be used to refine and override definitions made in annotations as well as resolve missing pieces (for example pointcut definitions referenced but not defined) in the annotation definition. See the Choosing a definition model section for best practices.
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.
Even if all definition is made using annotations (and nothing in XML) a tiny deployment descriptor in XML still has to be written. This is needed for the runtime system to know which aspects it should load. See the XML deployment descriptor section for more information on how to write such a deployment descriptor.
When defining an aspect using annotations, the deployment model is specified as metadata argument
of the metadata tag
@Aspect
.
@Aspect
class level metadata has an optional anonymous parameter which specifies the
deployment model. The default is
perJVM
if not specified.
@Aspect("perJVM")
- deploys the aspect as perJVM. This is the default if only
@Aspect
is specified.
@Aspect("perClass")
- deploys the aspect as perClass.
@Aspect("perInstance")
- deploys the aspect as perInstance.
name=
named parameter which specify the name of the aspect.
The default is the aspect class name if not specified.
@Aspect
at all and define the deployment model in the
XML descriptor.
In the aspect class you put the pointcut definitions as fields of the type
org.codehaus.aspectwerkz.definition.Pointcut
along with metadata annotations which specifies the type of (staticinitialization, execution, call, set, get, cflow or handler)
and pattern for the pointcut.
If the pointcut is used to retain parameters value of the matching target (method/constructor execution/call and field value set)
it has to be defined as a method, with a signature that conforms to the
args()
selector in the expression.
For details see the
Pointcuts section.
Define the type of the advice using annotation for the method along with the pointcut to which it the advice should be bound.
One of
@Around
,
@Before
,
@After
,
@AfterFinally
,
@AfterReturning
or
@AfterThrowing
annotations needs to be is specified.
For details see the
Advice section.
Source sample
The following source sample gives the main ideas of the annotation defined Aspect.
Java 5 annotations
@Aspect("perInstance") public class MyAspect { @Introduce("within(com.mypackage.*)") MarkerInterface anIntroduction; @Expression("execution(* com.mypackage.Target.*(..))") Pointcut pc1; @Around("pc1") public Object advice1(final JoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some other stuff return result; } @Around("call(* com.mypackage.Target.*(..))") public Object advice2(final JoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some other stuff return result; } @Mixin("within(com.mypackage..*)") public static class MyIntroduction extends SuperMixin implements ContractualInterface { ... // introduced methods and fields } }
JavaDoc annotations
/** * @Aspect("perInstance") */ public class MyAspect { /** * @Introduce("within(com.mypackage.*)") */ MarkerInterface anIntroduction; /** * @Expression("execution(* com.mypackage.Target.*(..))") */ Pointcut pc1; /** * @Around("pc1") */ public Object advice1(final JoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some other stuff return result; } /** * @Around("call(* com.mypackage.Target.*(..))") */ public Object advice2(final JoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some other stuff return result; } /** * @Mixin("within(com.mypackage..*)") */ public static class MyIntroduction extends SuperMixin implements ContractualInterface { ... // introduced methods and fields } }
In the aspect class you specify the introduction to add as field for interface introduction (marker interface with no required method) or as public inner-class for mixins.
Interface introduction are added by adding a field to the aspect. The field type has to be the
interface that you want to introduce. The
@Introduce
annotation is then used to specify
the pattern matching the classes you want to apply the interface to.
For details see the
Introductions section.
Interface introduction with implementation, also known as mixins, are added by adding a public static inner-class to the aspect class. For details see the Introductions section.
The
Pointcut
class implements the pointcut concept.
A Pointcut picks out join points, i.e. selects well-defined points in the program flow.
This section describes how to define named pointcuts using annotations, for a detailed description on how pointcuts are written and composed see the Pointcut definition and Pointcut composition sections.
Named pointcuts are defined as fields in the Aspect class, or as method if they have a signature
(that is using the
args()
selector), and they have to follow the following requirements:
org.codehaus.aspectwerkz.definition.Pointcut
and should be
declared in the aspect class or hierarchy.
@Expression
annotation allows us to define the type and pattern of the pointcut.
There can be a composition, whether or not anonymous like in
@Expression("anExpression OR call(<call pattern>)")
args()
selector, the pointcut is defined as a method. The return type
of the method does not matter, but the signature must conform to the
args()
selector
(see samples below).
The name of the pointcut is the field name if a field, or the method name if a method.
Java 5 annotations
//-- defined as fields @Expression("execution(* com.package..*.*(..))") Poincut allMethods; @Expression("set(* com.package.Constants.*)") Pointcut allConstantAccess; //-- defined as methods @Expression("execution(* com.package..*.*(..)) && args(s)") Poincut allMethodsWithStringArg(String s) {return null;} @Expression("execution(* com.package..*.*(..)) && args(i, s, String ..)") void allMethodsWithIntArgTwoStringArgsAndSomeOtherArgs(int i, String s) {}
JavaDoc annotations
//-- defined as fields /** * @Expression("execution(* com.package..*.*(..))") */ Poincut allMethods; /** * @Expression("set(* com.package.Constants.*)") */ Pointcut allConstantAccess; //-- defined as methods /** * @Expression("execution(* com.package..*.*(..)) && args(s)") */ Poincut allMethodsWithStringArg(String s) {return null;} /** * @Expression("execution(* com.package..*.*(..)) && args(i, s, String ..)") */ void allMethodsWithIntArgTwoStringArgsAndSomeOtherArgs(int i, String s) {}
The advice are implemented as regular methods in the Aspect class with the following requirements:
public Object <name of method>(JoinPoint joinPoint) throws Throwable
(the public modifier is not mandatory, choose according to your needs)
unless...
args()
). In such a case, the
parameter referenced in the pointcut signature must appear in the advice signature.
The JoinPoint parameter must appear although not referenced by the pointcut signature.
We can thus have any signature.
The supported types of advice are the following.
@Around <poincut expression>
- is invoked "around" the
join point. Can be used to intercept method invocations on the 'callee' side.
@Before <poincut expression>
- is invoked before the join
point. Can be used for advising fields or method invocations on the 'caller' side.
@After <poincut expression>
- is invoked after the join
point. Can be used for advising fields or method invocations on the 'caller' side.
@AfterFinally <poincut expression>
- same as @After (invoked after the join point).
@AfterReturning <poincut expression>
- is invoked after a method normal return (no exception
thrown)
@AfterThrowing <poincut expression>
- is invoked after a method return with an exception.
Both anonymous pointcut expressions and expressions made out of named pointcuts are supported. See example below.
Here is a simple example of an
Around
advice using Java 5 annotation style, the definition would be the same using
JavaDoc annotations. (For more examples see the
Examples section.)
Java 5 annotations
//-- no arguments, JoinPoint is implictly mandatory /** * Non-anonymous expression (pc1 and pc2 are fields of type Pointcut with metadata). */ @Around("pc1 && pc2") public Object myAroundAdvice1(StaticJoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some more stuff return result; } /** * Anonymous pointcuts. */ @Around("execution(* com.package.Target.*(..)) || call(* com.package.Target.*(..))") public Object myAroundAdvice2(StaticJoinPoint joinPoint) throws Throwable { // do some stuff Object result = joinPoint.proceed(); // do some more stuff return result; } //-- pointcut with signatures /** * Non-anonymous expression (pc1 is a method defining a Pointcut, with a String parameter). * Note the use of "s" in the metadata expression. */ @Around("pc1(s)") public Object myAroundAdvice1WithArg(StaticJoinPoint joinPoint, String s) throws Throwable { // do some stuff // "s" can be accessed without the use of RTTI // Note that it can be modified according to the rules of Java Language references passing System.out.println(s); Object result = joinPoint.proceed(); // do some more stuff return result; } /** * Anonymous pointcuts. */ @Around("execution(* com.package.Target.*(..)) && args(arg0, int[][], arg2)") public Object myAroundAdvice2WithArg(StaticJoinPoint joinPoint, String arg0, com.Foo arg2) throws Throwable { // do some stuff // access arg0, arg2 arg2.doSomething(); Object result = joinPoint.proceed(); // do some more stuff return result; }
AspectWerkz
supports two types of introductions
Using annotations, introductions are defined as:
@Introduce <pointcut>
, where pointcut is composed
with within, hasfield, hasmethod.
@Mixin <pointcut>
, where pointcut is composed
with within, hasField, hasMethod.
Interface introductions (introduction of marker interfaces) are defined as fields in the aspect class.
The type of the field is the interface to introduce.
The field is marked with the annotation
@Introduce <pointcut>
to specify to which
classes the introduction applies.
The introduction type is the field type.
When using abstract aspects or aspect inheritance, the aspect's super class can define interface
introductions without specifying a
@Introduce <pointcut>
metadata.
The concrete aspect should thus override the fields and specify the metadata defining to which
classes the introduction applies.
Java 5 annotations
@Introduce("within(com.package.*)") protected Serializable introduction1; @Introduce("within(com.package.*)") public OtherMarkerInterface introduction2;
JavaDoc annotations
/** * @Introduce("within(com.package.*)") */ protected Serializable introduction1; /** * @Introduce("within(com.package.*)") */ public OtherMarkerInterface introduction2;
This section references all annotations used in the annotation definition.
Annotations | level | defines | anonymous value | parameter(s) |
---|---|---|---|---|
[O:defaultValue] for optional parameter with default value | ||||
@Aspect | class | Aspect | perJVM | perClass | perInstance [O:perJVM] | name=[0:<aspect class name>] |
@Expression | Pointcut field, method with matching signature | Pointcut, method execution | <pointcut expression> | |
@Introduce | field | Interface Introduction | <within/hasfield/hasmethod pointcut (and composition of those)> | |
@Mixin | class or inner class | Mixin | <within/hasxxx pointcut> | deploymentModel=(perJVM | perClass | perInstance [O:perInstance]), isTransient=(true | false [O:false]) |
@Around | method | around advice | <pointcut expression> | |
@Before | method | before advice | <pointcut expression> | |
@After | method | after finally advice | <pointcut expression> | |
@AfterFinally | method | after finally advice | <pointcut expression> | |
@AfterReturning | method | after returning advice | <pointcut expression> | the FQN of the return type (optional) |
@AfterThrowing | method | after throwing advice | <pointcut expression> | the FQN of the exception type (optional) |
@<name> | any | custom annotation (typed or untyped) |
JavaDoc annotation defined aspects have to be post-compiled to incorporate the metadata
into the class file bytecode.
This post-compilation step will not be needed with Java 5 annotation defined aspects.
For now you need to first compile
the aspect classes with javac compiler and then post compile the
.class
files with the aspect source code as input.
To post-compile the aspect's
.class
files you have to use the
AnnotationC
compiler, which needs both the regular
.class
files and the aspect sources files
containing the annotation metadata. You can run the
AnnotationC
from the command line.
(It might be useful to run the
ASPECTWERKZ_HOME/bin/setEnv.{bat|sh}
script first.)
java [options...] org.codehaus.aspectwerkz.annotation.AnnotationC -src <path to src dirs> -srcpath <list of files> -srcincludes <path to file> -dest <path to destination dir> -custom <property file for cutom annotations> -verbose
Description of compiler options
Note: only one of -src
, -srcpath
and -srcincludes
may be used.
-verbose
- (optional) activates compilation status information
-src <path to src dirs>
- provides the list of source directories separated
by File.pathSeparator
-srcpath <list of files>
- provides a comma separated list of source files
-srcincludes <path to file>
- provides the path to a file containing the list
of source files (one name per line)
-dest <path to destination dir>
- (optional) if omitted the compiled
classes will be written to the initial directory
-custom <property file for cutom annotations>
- (optional) only needed
if you have custom annotations you want to compile
AnnotationC
can also be used to handle custom annotations as described
here.
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>
We also have an Ant task for the AnnotationC compiler.
For details see this this section.
The deployment descriptor is needed for the runtime system to know which aspects it should load. It always has to be specified even though all definition is made in annotations.
Example:
<aspectwerkz> <system id="tests"> <package name="foo.bar"> <aspect class="MyAspect1"/> <aspect class="MyAspect2"/> ... </package> </system> </aspectwerkz>