Annotation definition - Self-defined Aspects

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.

Aspects

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.


  • an optional name= named parameter which specify the name of the aspect. The default is the aspect class name if not specified.


It is also possible to not declare the @Aspect at all and define the deployment model in the XML descriptor.

Define the pointcuts

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.

Define the introductions

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 advice

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
    }
}
                        

Pointcuts

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:

  1. If defined as a field, the field is of type org.codehaus.aspectwerkz.Pointcut and should be declared in the aspect class or hierarchy.


  2. The @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>)


  3. If using the 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).


  4. The name of the pointcut must be unique - that is its signature does not allow to differenciate to pointcut defined as method. The methods names must differ.

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) {}
                    

Advice

The advice are implemented as regular methods in the Aspect class with the following requirements:

  1. The signature of the method is public Object <name of method>(JoinPoint joinPoint) throws Throwable (the public modifier is not mandatory, choose according to your needs) unless...


  2. The advice is bounded to a pointcut with signature (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.


  3. The method has a metadata that specifies the advice type and the matching joinpoints through a pointcut expression. The matching joinpoints are whether defined as an anonymous pointcut (and not a as a field) whether as an algebraic expression composed by other poincuts, whose type matches the advice type.

    If the expression is referencing a pointcut with a signature, then the parameters names as defined in the method signature must be passed to it.


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;
    }
                    

Introductions

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:

  • Fields of the aspect class whose type is the interface to introduce.

    Use if the interface does not require any implementation (marker interface).

    Fields are marked with metadata @Implements <classPattern>.


  • Public inner class of the aspect class.

    The inner class declares to implement the interface(s) to introduce and effectively implements it. The inner class is marked with metadata @Introduce <classPattern>.

Interface introductions

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;
                    

Implementation introductions

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
        ...
    }
                    

Annotation reference

This section references all annotations used in the annotation definition.

Annotationsleveldefinesanonymous valueparameter(s)
[O:defaultValue] for optional parameter with default value
@AspectclassAspectperJVM | perClass | perInstance | perThread [O:perJVM]name [0:<aspect class name>]
@ExpressionPointcut field, method with matching signaturePointcut, method execution<pointcut expression>
@ImplementsfieldInterface Introduction<class pattern>
@Introduceinner class, class levelImplementation Introduction<class pattern>deploymentModel=perJVM | perClass | perInstance | perThread [O: aspect'one]
@Aroundmethodaround advice<pointcut expression>name [0:<aspect class mame>.<method name>
@Beforemethodbefore advice<pointcut expression>name [0:<aspect class mame>.<method name>
@Aftermethodafter advice<pointcut expression>name [0:<aspect class mame>.<method name>
@<name>anycustom annotation (typed or untyped)

Aspect annotation compilation

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>

XML deployment descriptor

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>