This section discusses changes to type pattern and signature pattern matching in AspectJ 5 that support matching join points based on the presence or absence of annotations. We then discuss means of exposing annotation values within the body of advice.
For any kind of annotated element (type, method, constructor, package, etc.), an annotation pattern can be used to match against the set of annotations on the annotated element.An annotation pattern element has one of two basic forms:
These simple elements may be negated using !
, and
combined by simple concatentation. The pattern @Foo @Boo
matches an annotated element that has both an annotation of type Foo
and an annotation of type Boo
.
Some examples of annotation patterns follow:
Matches any annotated element which has an annotation of
type Immutable
.
Matches any annotated element which does not have an annotation of
type Persistent
.
Matches any annotated element which has both an annotation of type Foo
and
an annotation of type Goo
.
Matches any annotated element which has either an annotation of a type matching
the type pattern (Foo || Goo)
.
In other words, an annotated element with either an
annotation of type Foo
or
an annotation of type Goo
(or both). (The parenthesis are required in this example).
Matches any annotated element which has either an annotation of a type matching
the type pattern (org.xyz..*)
.
In other words, an annotated element with an annotation that is declared in the
org.xyz package or a sub-package. (The parenthesis are required in this example).
AspectJ 1.5 extends type patterns to allow an optional AnnotationPattern
prefix.
TypePattern := SimpleTypePattern | '!' TypePattern | '(' AnnotationPattern? TypePattern ')' TypePattern '&&' TypePattern | TypePattern '||' TypePattern SimpleTypePattern := DottedNamePattern '+'? '[]'* DottedNamePattern := FullyQualifiedName RestOfNamePattern? | '*' NotStarNamePattern? RestOfNamePattern := '..' DottedNamePattern | '*' NotStarNamePattern? NotStarNamePattern := FullyQualifiedName RestOfNamePattern? | '..' DottedNamePattern FullyQualifiedName := JavaIdentifierCharacter+ ('.' JavaIdentifierCharacter+)*
Note that in most cases when annotations are used as part of a type pattern,
the parenthesis are required (as in (@Foo Hello+)
). In
some cases (such as a type pattern used within a within
or
handler
pointcut expression), the parenthesis are optional:
OptionalParensTypePattern := AnnotationPattern? TypePattern
The following examples illustrate the use of annotations in type patterns:
Matches any type with an @Immutable
annotation.
Matches any type which does not have an @Immutable
annotation.
Matches any type in the org.xyz
or org.abc
packages with the @Immutable
annotation.
Matches a type Foo
or any of its subtypes, which have the @Immutable
annotation, or a type Goo
.
Matches any type in a package beginning with the prefix org.xyz
,
which has either the @Immutable
annotation or the
@NonPersistent
annotation.
Matches any type in a package beginning with the prefix org.xyz
,
which has both an @Immutable
annotation and an
@NonPersistent
annotation.
Matches any type in a package beginning with the prefix org.xyz
,
which has an inheritable annotation. The annotation pattern
@(@Inherited *)
matches any annotation of a type matching the
type pattern @Inherited *
, which in turn matches any type with the
@Inherited
annotation.
A FieldPattern
can optionally specify an annotation-matching
pattern as the first element:
FieldPattern := AnnotationPattern? FieldModifiersPattern? TypePattern (TypePattern DotOrDotDot)? SimpleNamePattern FieldModifiersPattern := '!'? FieldModifier FieldModifiersPattern* FieldModifier := 'public' | 'private' | 'protected' | 'static' | 'transient' | 'final' DotOrDotDot := '.' | '..' SimpleNamePattern := JavaIdentifierChar+ ('*' SimpleNamePattern)?
If present, the AnnotationPattern
restricts matches to fields with
annotations that match the pattern. For example:
Matches a field of any type and any name, that has an annotation of
type @SensitiveData
Matches a member field of a type in a package with prefix org.xzy
,
where the field is of type List
, and has an annotation of type
@SensitiveData
Matches a member field of a type in a package with prefix org.xzy
,
where the field is of a type which has a @SensitiveData
annotation.
Matches a field with an annotation @Foo
, of a type with an
annotation @Goo
, declared in a type with annotation
@Hoo
.
Matches a field with an annotation @Persisted
and
an annotation @Classified
.
A MethodPattern
can optionally specify an annotation-matching
pattern as the first element.
MethodPattern := AnnotationPattern? MethodModifiersPattern? TypePattern (TypePattern DotOrDotDot)? SimpleNamePattern '(' FormalsPattern ')'ThrowsPattern? MethodModifiersPattern := '!'? MethodModifier MethodModifiersPattern* MethodModifier := 'public' | 'private' | 'protected' | 'static' | 'synchronized' | 'final' FormalsPattern := '..' (',' FormalsPatternAfterDotDot)* | OptionalParensTypePattern (',' FormalsPattern)* | TypePattern '...' FormalsPatternAfterDotDot := OptionalParensTypePattern (',' FormalsPatternAfterDotDot)* | TypePattern '...' ThrowsPattern := 'throws' TypePatternList TypePatternList := TypePattern (',' TypePattern)*
A ConstructorPattern
has the form
ConstructorPattern := AnnotationPattern? ConstructorModifiersPattern? (TypePattern DotOrDotDot)? 'new' '(' FormalsPattern ')' ThrowsPattern? ConstructorModifiersPattern := '!'? ConstructorModifier ConstructorModifiersPattern* ConstructorModifier := 'public' | 'private' | 'protected'
The optional AnnotationPattern
at the beginning of a
method or constructor pattern restricts matches to methods/constructors with
annotations that match the pattern. For example:
Matches a method with any return type and any name, that has an annotation of
type @Oneway
.
Matches a method with the @Transaction
annotation,
declared in a type with the @Persistent
annotation, and
in a package beginning with the org.xyz
prefix.
Matches any method taking at least one parameter, where the parameter
type has an annotation @Immutable
.
Matches any join point where the code executing is declared in a
type with an @Secure
annotation. The format of the within
pointcut designator
in AspectJ 5 is 'within' '(' OptionalParensTypePattern ')'
.
Matches the staticinitialization join point of any type with the
@Persistent
annotation. The format of the
staticinitialization
pointcut designator
in AspectJ 5 is 'staticinitialization' '(' OptionalParensTypePattern ')'
.
Matches a call to a method with a @Oneway
annotation.
The execution of any public method in a package with prefix
org.xyz
, where the method returns an
immutable result.
Matches the set of any cachable field.
Matches the handler join point for the handling of any exception that is
not Catastrophic
. The format of the handler
pointcut designator in AspectJ 5 is 'handler' '(' OptionalParensTypePattern ')'
.
AspectJ 5 supports a set of "@" pointcut designators which
can be used both to match based on the presence of an annotation at
runtime, and to expose the annotation value as context in a pointcut or
advice definition. These designators are @args, @this, @target,
@within, @withincode
, and @annotation
It is a compilation error to attempt to match on an annotation type
that does not have runtime retention using @this, @target
or @args
. It is a compilation error to attempt to use
any of these designators to expose an annotation value that does not
have runtime retention.
The this()
, target()
, and
args()
pointcut designators allow matching based
on the runtime type of an object, as opposed to the statically
declared type. In AspectJ 5, these designators are supplemented
with three new designators : @this()
(read, "this
annotation"), @target()
, and @args()
.
Like their counterparts, these pointcut designators can be used both for join point matching, and to expose context. The format of these new designators is:
AtThis := '@this' '(' AnnotationOrIdentifer ')' AtTarget := '@target' '(' AnnotationOrIdentifier ')' AnnotationOrIdentifier := FullyQualifiedName | Identifier AtArgs := '@args' '(' AnnotationsOrIdentifiersPattern ')' AnnotationsOrIdentifiersPattern := '..' (',' AnnotationsOrIdentifiersPatternAfterDotDot)? | AnnotationOrIdentifier (',' AnnotationsOrIdentifiersPattern)* | '*' (',' AnnotationsOrIdentifiersPattern)* AnnotationsOrIdentifiersPatternAfterDotDot := AnnotationOrIdentifier (',' AnnotationsOrIdentifiersPatternAfterDotDot)* | '*' (',' AnnotationsOrIdentifiersPatternAfterDotDot)*
The forms of @this()
and @target()
that
take a single annotation name are analogous to their counterparts that take
a single type name. They match at join points where the object bound to
this
(or target
, respectively) has an
annotation of the specified type. For example:
Matches any join point where the object currently bound to 'this'
has an annotation of type Foo
.
Matches a call to any object where the target of the call has
a @Classified
annotation.
Annotations can be exposed as context in the body of advice by
using the forms of @this(), @target()
and
@args()
that use bound variables in the place
of annotation names. For example:
pointcut callToClassifiedObject(Classified classificationInfo) : call(* *(..)) && @target(classificationInfo); pointcut txRequiredMethod(Tx transactionAnnotation) : execution(* *(..)) && @this(transactionAnnotation) && if(transactionAnnotation.policy() == TxPolicy.REQUIRED);
The @args
pointcut designator behaves as its args
counterpart, matching join points based on number and position of arguments, and
supporting the *
wildcard and at most one ..
wildcard. An annotation at a given position in an @args
expression
indicates that the runtime type of the argument in that position at a join point must
have an annotation of the indicated type. For example:
/** * matches any join point with at least one argument, and where the * type of the first argument has the @Classified annotation */ pointcut classifiedArgument() : @args(Classified,..); /** * matches any join point with three arguments, where the third * argument has an annotation of type @Untrusted. */ pointcut untrustedData(Untrusted untrustedDataSource) : @args(*,*,untrustedDataSource);
In addition to accessing annotation information at runtime through context binding,
access to AnnotatedElement
information is also available
reflectively with the body of advice through the thisJoinPoint
,
thisJoinPointStaticPart
, and
thisEnclosingJoinPointStaticPart
variables. To access
annotations on the arguments, or object bound to this or target at a join
point you can use the following code fragments:
Annotation[] thisAnnotations = thisJoinPoint.getThis().getClass().getAnnotations(); Annotation[] targetAnnotations = thisJoinPoint.getTarget().getClass().getAnnotations(); Annotation[] firstParamAnnotations = thisJoinPoint.getArgs()[0].getClass().getAnnotations();
The @within
and @withincode
pointcut designators
match any join point where the executing code is defined within a type (@within
),
or a method/constructor (@withincode
) that has an annotation of the specified
type. The form of these designators is:
AtWithin := '@within' '(' AnnotationOrIdentifier ')' AtWithinCode := '@withincode' '(' AnnotationOrIdentifier ')'
Some examples of using these designators follow:
Matches any join point where the executing code is defined
within a type which has an annotation of type Foo
.
Matches any join point where the executing code is defined
in a method or constructor which has an annotation of type @Critical
,
and exposes the value of the annotation in the parameter
c
.
The @annotation
pointcut designator matches any
join point where the subject of the join point has
an annotation of the given type. Like the other @pcds, it can also be
used for context exposure.
AtAnnotation := '@annotation' '(' AnnotationOrIdentifier ')'
The subject of a join point is defined in the table in chapter one of this guide.
Access to annotation information on members at a matched join point is also available
through the getSignature
method of the JoinPoint
and JoinPoint.StaticPart
interfaces. The Signature
interfaces are extended with additional operations that provide access to the
java.lang.reflect
Method, Field
and
Constructor
objects on which annnotations can be queried. The following fragment
illustrates an example use of this interface to access annotation information.
Signature sig = thisJoinPointStaticPart.getSignature(); AnnotatedElement declaringTypeAnnotationInfo = sig.getDeclaringType(); if (sig instanceof MethodSignature) { // this must be a call or execution join point Method method = ((MethodSignature)sig).getMethod(); }
Note again that it would be nicer to add the method getAnnotationInfo directly to MemberSignature, but this would once more couple the runtime library to Java 5.
The @this,@target
and @args
pointcut designators can only be used to match against annotations
that have runtime retention. The @within, @withincode
and @annotation
pointcut designators can only be used
to match against annotations that have at least class-file retention, and
if used in the binding form the annotation must have runtime retention.
Matching on package annotations is not supported in AspectJ. Support for this capability may be considered in a future release.
Parameter annotation matching is being added in AspectJ1.6. Initially only matching is supported but binding will be implemented at some point. Whether the annotation specified in a pointcut should be considered to be an annotation on the parameter type or an annotation on the parameter itself is determined through the use of parentheses around the parameter type. Consider the following:
@SomeAnnotation class AnnotatedType {} class C { public void foo(AnnotatedType a) {} public void goo(@SomeAnnotation String s) {} }
The method foo has a parameter of an annotated type, and can be matched by this pointcut:
pointcut p(): execution(* *(@SomeAnnotation *));
When there is a single annotation specified like this, it is considered to be part of the type pattern in the match against the parameter: 'a parameter of any type that has the annotation @SomeAnnotation'.
To match the parameter annotation case, the method goo, this is the pointcut:
pointcut p(): execution(* *(@SomeAnnotation (*)));
The use of parentheses around the wildcard is effectively indicating that the annotation should be considered separately to the type pattern for the parameter type: 'a parameter of any type that has a parameter annotation of @SomeAnnotation'.
To match when there is a parameter annotation and an annotation on the type as well:
pointcut p(): execution(* *(@SomeAnnotation (@SomeOtherAnnotation *)));
The parentheses are grouping @SomeOtherAnnotation with the * to form the type pattern for the parameter, then the type @SomeAnnotation will be treated as a parameter annotation pattern.
According to the Java 5 specification, non-type annotations are not
inherited, and annotations on types are only inherited if they have the
@Inherited
meta-annotation.
Given the following program:
class C1 { @SomeAnnotation public void aMethod() {...} } class C2 extends C1 { public void aMethod() {...} } class Main { public static void main(String[] args) { C1 c1 = new C1(); C2 c2 = new C2(); c1.aMethod(); c2.aMethod(); } } aspect X { pointcut annotatedC2MethodCall() : call(@SomeAnnotation * C2.aMethod()); pointcut annotatedMethodCall() : call(@SomeAnnotation * aMethod()); }
The pointcut annotatedC2MethodCall
will not match anything
since the definition of aMethod
in C2
does not have the annotation.
The pointcut annotatedMethodCall
matches
c1.aMethod()
but not c2.aMethod()
. The call
to c2.aMethod
is not matched because join point matching for
modifiers (the visibility modifiers, annotations, and throws clause) is based on
the subject of the join point (the method actually being called).
The if
pointcut designator can be used to write pointcuts
that match based on the values annotation members. For example:
pointcut txRequiredMethod(Tx transactionAnnotation) : execution(* *(..)) && @this(transactionAnnotation) && if(transactionAnnotation.policy() == TxPolicy.REQUIRED);