What is important to understand is that both the Annotation and the XML definition are both 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.
Even if all definition is made using annotations (and nothing in XML) an 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
. (The current implementation uses a doclet syntax.
AspectWerkz
will support JSR-175 syntax as well, as soon as it is available.)
@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.
@Aspect perThread
- deploys the aspect as perThread.
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.Pointcut
along with metadata annotations which specifies the type of (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.
Pointcuts section.
In the aspect class you specify the introduction to add as field for pure interface introduction (marker interface with no required method) or as public inner-class for interface and implementation introduction.
Pure 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
metadata is then used to specify
the pattern matching the classes you want the introduction to be applied to.
See
Introductions section.
Interface introduction with implementation, also known as mixins, are added by adding a public inner-class to the aspect class.
The inner-class is then the default introduction/mixin implementation. It can extend any class you want, just like any other regular Java class.
The
@Implements
metadata specifies the pattern matching the classes you want the introduction/mixin to be applied to.
The interface(s) introduced can be whether be set explicitly by letting the inner-class implement the interface(s), whether implicitly
by inheriting another class which is itself implementing the interface(s).
This can be handy when distributing reusable aspects.
See
Introductions section.
Define the type of the advice using metadata for the method along with the pointcut to which it the advice should be bound.
One of
@Around
,
@Before
or
@After
annotations needs to be is specified.
See the
Advice section for details.
Source sample
The following source sample gives the main ideas of the self-defined Aspect. Please remember that the metadata markers are inserted in the bytecode through a post-compilation step which won't be needed under Java 1.5.
/** * @Aspect perInstance */ public class MyAspect { /** * @Implements 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; } /** * @Introduce com.mypackage.* */ public static class MyIntroduction extends SuperMixin implements ContractualInterface { ... // introduced methods and fields } }
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.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.
//-- 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.
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.
(For more examples see the
Examples section.)
//-- 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(JoinPoint 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(JoinPoint 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(JoinPoint 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(JoinPoint 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; }
The Introduction class implements the concept of Mixins or Open Classes. I.e. an Introduction makes it possible to extend a class with a new interface and/or a new implementation (methods and fields).
Using annotations, introductions are defined as:
@Implements <classPattern>
.
@Introduce <classPattern>
.
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 metadata
@Implements <classPattern>
to specify to which
classes the introduction applies.
The name of the introduction is the field name.
When using abstract aspects or aspect inheritance, the aspect's super class can define interface
introductions without specifying a
@Implements <classPattern>
metadata.
The concrete aspect should thus override the fields and specify the metadata defining to which
classes the introduction applies.
/** * @Implements com.package.* */ protected Serializable introduction1; /** * @Implements com.package.* */ public OtherMarkerInterface introduction2;
The inner mixin class is marked with metadata
@Introduce <classPattern>
to specify to which classes the introduction applies.
The
@Introduce
annotation accepts an optional
deploymentModel=
parameter to specify the
introduction deployment model. If not specified the aspect deployment model applies.
See
deployment model section.
The name of the introduction is the fully qualified inner class name e.g.
<aspectClassName>$<innerClassName>
.
When using abstract aspects or aspect inheritance, the aspect's super class can define introductions without
specifying a
@Introduce <classPattern>
metadata.
The concrete aspect should thus declare an inner class that extends the super class aspect one's and
specify through metadata to which classes the introduction applies.
/** * Anonymous pointcut. * * @Introduce within(com.package.*) */ public static class MyIntroduction extends SuperIntroduction implements ToBeIntroduced { // introduced methods implementation ... } /** * @Expression within(com.package.Foo) */ Pointcut pc1; /** * Named pointcut(s) in expression. * Note: in this specific case we will not match a class named "pc1" but we will look for a named pointcut "pc1" * * @Introduce pc1 */ public static class AnotherIntroduction implements AnotherToBeIntroduced { // introduced methods implementation ... }
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 | perThread [O:perJVM] | name [0:<aspect class name>] |
@Expression | Pointcut field, method with matching signature | Pointcut, method execution | <pointcut expression> | |
@Implements | field | Interface Introduction | <class pattern> | |
@Introduce | inner class, class level | Implementation Introduction | <class pattern> | deploymentModel=perJVM | perClass | perInstance | perThread [O: aspect'one] |
@Around | method | around advice | <pointcut expression> | name [0:<aspect class mame>.<method name> |
@Before | method | before advice | <pointcut expression> | name [0:<aspect class mame>.<method name> |
@After | method | after advice | <pointcut expression> | name [0:<aspect class mame>.<method name> |
@<name> | any | custom annotation (typed or untyped) |
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 1.5 and JSR-175. 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
AspectC
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 [-verbose] -src <path to src dir> -classes <path to classes dir> [-dest <path to destination dir>] [-custom <property file for custom annotations>]
-verbose
- (optional) to activate verbose logging of compilation
-src <path to src dir>
- the path directory
for the source files (not mandatory; it is not needed to point to the root package directory
since package name is extracted from the source file itself)
-classes <path to classes dir>
- the path to the root directory of the regular
.class
files for the aspects
-dest <path to destination dir>
- (optional)
- the path to the root directory to write the new
.class
files for the self-defined aspects.
If not specified, the initial
.class
files are overridden.
-custom <property file for custom annotations>
- optional,
needed to compile custom annotations.
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>
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>