This section discusses changes to type pattern and signature pattern matching in AspectJ 1.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.
In AspectJ 1.2, type patterns have the following form:
TypePattern := IdPattern | '!' TypePattern | TypePattern '&&' TypePattern | TypePattern '||' TypePattern | TypePattern '+' | TypePattern ('[]')* | '(' TypePattern ')' IdPattern := SimpleNamePattern | SimpleNamePattern '.' SimpleNamePattern | SimpleNamePattern '..' SimpleNamePattern SimpleNamePattern := ('*' | JavaIdentifierCharacter)+
Java 5 (and hence AspectJ 1.5) allows annotations on both types and packages, and type patterns in AspectJ 1.5 are extended to allow matching based on such annotations:
TypePattern := PackagePattern? NestedTypeNamesPattern | '!' TypePattern | TypePattern '&&' TypePattern | TypePattern '||' TypePattern | TypePattern '+' | TypePattern ('[]')* | '(' TypePattern ')' PackagePattern := QualifiedNamePattern DotSeparator | '(' AnnotationPattern QualifiedNamePattern DotSeparator ')' DotSeparator := '.' | '..' QualifiedNamePattern := SimpleNamePattern | SimpleNamePattern DotSeparator SimpleNamePattern SimpleNamePattern := ('*' | JavaIdentifierCharacter)+ AnnotationPattern := AnnotationName | '!' AnnotationName | AnnotationName '&&' AnnotationName | AnnotationName '||' AnnotationName | '(' AnnotationPattern ')' AnnotationName := '@' JavaIdentifierCharacter+ ('.' JavaIdentifierCharacter+)* NestedTypeNamesPattern := MaybeAnnotatedNamePattern | MaybeAnnotatedNamePattern DotSeparator MaybeAnnotatedNamePattern MaybeAnnotatedNamePattern := SimpleNamePattern | AnnotationPattern SimpleNamePattern | '( MaybeAnnotatedNamePattern ')'
The following examples illustrate the use of annotations in type patterns.
Matches any type which has an annotation of type SomeAnnotation.
Matches any type which has eithir an annotation of type MyAnnotation or an annotation of type YourAnnotation.
Matches any type which does not have an annotation of type SomeAnnotation.
Matches any type declared in a package beginning with the prefix org.xyz and which has an annotation of type SomeAnnotation.
Matches any type in a package beginning with the prefix org.xyz, where the package has an annotation of type SomeAnnotation.
Matches any type in a package with a package annotation of type SomeAnnotation.
Matches any type in the package org.xyz.abc that has an annotation of type SomeAnnotation, or any nested type within such a type.
Matches any type with an annotation of type SomeAnnotation, in the set of types comprised of BankAccount and all types extending BankAccount.
Matches a type BankAccount with an annotation of type SomeAnnotation, or any subtype of such a type.
A FieldPattern is described by the following grammar:
FieldPattern := AnnotationPattern? FieldModifiersPattern? TypePattern (TypePattern '.')? SimpleNamePattern FieldModifiersPattern := FieldModifier* FieldModifier := 'public' | 'private' | 'protected' | 'static' | 'transient' | 'final'
The optional AnnotationPattern restricts matches to fields with annotations that match the pattern. For example:
A MethodPattern is of the form
MethodPattern := AnnotationPattern? ModifiersPattern? TypePattern (TypePattern '.')? SimpleNamePattern '(' FormalsPattern ')' ThrowsPattern? ModifiersPattern := Modifier* Modifier := 'public' | 'private' | 'protected' | 'static' | 'synchronized' | 'final' FormalsPattern := '..' (',' FormalsPatternAfterDotDot)* | Formal (',' FormalsPattern)* FormalsPatternAfterDotDot := Formal (',' FormalsPatternAfterDotDot)* Formal := AnnotationPattern '(' TypePattern ')' | TypePattern ThrowsPattern := 'throws' TypePatternList TypePatternList := TypePattern (',' TypePattern)*
A ConstructorPattern has the form
ConstructorPattern := AnnotationPattern? ModifiersPattern? TypePattern (TypePattern '.')? 'new' '(' FormalsPattern ')' ThrowsPattern?
The optional AnnotationPattern at the beginning of a method or constructor patterns restricts matches to methods/constructors with annotations that match the pattern. For example:
Since Java 5 allows parameters to be annotated as well as types, method and constructor patterns in AspectJ 1.5 also supporting matching of signatures based on parameter annotations. For example:
Matches a method taking a single argument, where the parameter has an annotation of type Sensitive. E.g.
public byte[] encrypt(@Sensitive byte[] data) {...}
Matches a method defined in the InsurancePolicy type or one of its subtypes, taking at least one argument, where the parameter has an annotation of type Sensitive, and the parameter type has an annotation of type Foo.
Matches any join point occuring in a package with the @Secure annotation.
Matches the staticinitialization join point of any type with the @Persistent annotation.
The execution of any public method in a package with prefix org.xyz, where the method returns an immutable result.
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 1.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:
ThisAnnotation := 'this@' '(' AnnotationNameOrVar ')' TargetAnnotation := 'target@' '(' AnnotationNameOrVar ')' ArgsAnnotation := 'args@' '(' ArgsAnnotationPattern ')' ArgsAnnotationPattern := AnnotationNameOrVar (',' ArgsAnnotationPattern)? | '*' (',' ArgsAnnotationPattern)? | '..' (',' SingleArgsAnnotationPattern)* SingleArgsAnnotationPattern := AnnotationNameOrVar (',' SingleArgsAnnotationPattern)? | '*' (',' SingleArgsAnnotationPattern)? AnnotationNameOrVar := AnnotationName | JavaIdentifierCharacter+
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:
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);
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);
We could use @this, @target, and @args instead. These forms don't read as well to me, but they may look more 'familiar' in the program source.
TODO: We need to extend org.aspectj.lang.JoinPoint too. It would be nice to use the AnnotatedElement interface, but this would create a hard 1.5 dependency in our runtime library and I don't think we want to do that (or certainly not do it lightly).
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 annotatedMethodCall() : call(@SomeAnnotation * C1.aMethod()); pointcut c1MethodCall() : call(* C1.aMethod()); }
The pointcut annotatedMethodCall will match the call to c1.aMethod(), but not the call to c2.aMethod().
The pointcut c1MethodCall matches both c1.aMethod() and c2.aMethod().
AspectJ 1.5 allows you to annotate advice, but there is no way to qualify advice execution join point matching based on the presence of annotations.
It would be useful to be able to match join points based on annotation values, rather than merely the presence of an annotation of a given type. This facility may be supported in a future version of AspectJ.