This section provides the essential information about annotations in
Java 5 needed to understand how annotations are treated in AspectJ 5.
For a full introduction to annotations in Java, please see the
documentation for the Java 5 SDK.
Java 5 introduces annotation types which can
be used to express metadata relating to program members in the
form of annotations. Annotations in Java 5
can be applied to package and type declarations (classes,
interfaces, enums, and annotations), constructors, methods,
fields, parameters, and variables. Annotations are specified in the
program source by using the @ symbol. For example,
the following piece of code uses the @Deprecated
annotation to indicate that the obsoleteMethod()
has been deprecated:
@Deprecated
public void obsoleteMethod() { ... }
Annotations may be marker annotations,
single-valued annotations, or
multi-valued annotations.
Annotation types with no members or that provide default values
for all members may be used simply as marker annotations, as in
the deprecation example above. Single-value annotation types have
a single member, and the annotation may be written in one of
two equivalent forms:
@SuppressWarnings({"unchecked"})
public void someMethod() {...}
or
@SuppressWarnings(value={"unchecked"})
public void someMethod() {...}
Multi-value annotations must use the member-name=value
syntax to specify annotation values. For example:
@Authenticated(role="supervisor",clearanceLevel=5)
public void someMethod() {...}
Annotations can have one of three retention policies:
- Source-file retention
Annotations with source-file retention are read by the
compiler during the compilation process, but are not
rendered in the generated .class files.
- Class-file retention
This is the default retention policy. Annotations
with class-file retention are read by the compiler
and also retained in the generated
.class files.
- Runtime retention
Annotations with runtime retention are read by the
compiler, retained in the generated
.class files, and also made available
at runtime.
Local variable annotations are not retained in class files (or at runtime)
regardless of the retention policy set on the annotation type. See JLS 9.6.1.2.
Accessing Annotations at Runtime
Java 5 supports a new interface,
java.lang.reflect.AnnotatedElement, that is
implemented by the reflection classes in Java (Class,
Constructor,
Field, Method, and
Package). This interface gives you access
to annotations that have runtime retention via
the getAnnotation, getAnnotations,
and isAnnotationPresent. Because annotation types are
just regular Java classes, the annotations returned by these methods
can be queried just like any regular Java object.
It is important to understand the rules relating to inheritance of
annotations, as these have a bearing on join point matching
based on the presence or absence of annotations.
By default annotations are not inherited. Given
the following program
@MyAnnotation
class Super {
@Oneway public void foo() {}
}
class Sub extends Super {
public void foo() {}
}
Then Sub does not have
the MyAnnotation annotation, and
Sub.foo() is not an @Oneway
method, despite the fact that it overrides
Super.foo() which is.
If an annotation type has the meta-annotation @Inherited
then an annotation of that type on a class will cause
the annotation to be inherited by sub-classes. So, in the example
above, if the MyAnnotation type had the
@Inherited attribute, then Sub
would have the MyAnnotation annotation.
@Inherited annotations are not inherited when used to
annotate anything other than a type. A type
that implements one or more interfaces never inherits any annotations from
the interfaces it implements.
Join Point Matching based on Annotations
Note: compared to the previous version, this version restricts the
use of annotations in type patterns (package annotations and outer type annotations
cannot be specified inline), and requires parenthesis more often. These changes were
made to make pointcut expressions easier to read and interpret.
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. Annotation patterns are defined by the following
grammar.
AnnotationPattern := '!'? '@' AnnotationTypePattern AnnotationPattern*
AnnotationTypePattern := FullyQualifiedName |
'(' TypePattern ')'
FullyQualifiedName := JavaIdentifierCharacter+ ('.' JavaIdentifierCharacter+)*
In simple terms, an annotation pattern element has one of two basic
forms:
- @<qualified-name>, for example, @Foo, or
@org.xyz.Foo.
- @(<type-pattern>), for example, @(org.xzy..*), or
@(Foo || Boo)
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:
@Immutable
Matches any annotated element which has an annotation of
type Immutable.
!@Persistent
Matches any annotated element which does not have an annotation of
type Persistent.
@Foo @Goo
Matches any annotated element which has both an annotation of type Foo and
an annotation of type Goo.
@(Foo || 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).
@(org.xyz..*)
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. (Extensions to this definition for generics are shown in the next chapter).
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 this
pointcut expression, the parenthesis are optional:
OptionalParensTypePattern := AnnotationPattern? TypePattern
The following examples illustrate the use of annotations in type
patterns:
(@Immutable *)
Matches any type with an @Immutable annotation.
(!@Immutable *)
Matches any type which does not have an @Immutable annotation.
(@Immutable (org.xyz.* || org.abc.*))
Matches any type in the org.xyz or org.abc
packages with the @Immutable annotation.
((@Immutable Foo+) || Goo)
Matches a type Foo or any of its subtypes, which have the @Immutable
annotation, or a type Goo.
((@(Immutable || NonPersistent) org.xyz..*)
Matches any type in a package beginning with the prefix org.xyz,
which has either the @Immutable annotation or the
@NonPersistent annotation.
(@Immutable @NonPersistent org.xyz..*)
Matches any type in a package beginning with the prefix org.xyz,
which has both an @Immutable annotation and an
@NonPersistent annotation.
(@(@Inherited *) org.xyz..*)
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 is described by the following
grammar:
FieldPattern :=
AnnotationPattern? FieldModifiersPattern?
TypePattern (TypePattern DotOrDotDot)? SimpleNamePattern
FieldModifiersPattern := '!'? FieldModifier FieldModifiersPattern*
FieldModifier := 'public' | 'private' | 'protected' | 'static' |
'transient' | 'final'
DotOrDotDot := '.' | '..'
SimpleNamePattern := JavaIdentifierChar+ ('*' SimpleNamePattern)?
The optional AnnotationPattern restricts matches to fields with
annotations that match the pattern. For example:
- @SensitiveData * *
Matches a field of any type and any name, that has an annotation of
type @SensitiveData
- @SensitiveData List org.xyz..*.*
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
- (@SensitiveData *) org.xyz..*.*
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.
- @Foo (@Goo *) (@Hoo *).*
Matches a field with an annotation @Foo, of a type with an
annotation @Goo, declared in a type with annotation
@Hoo.
- @Persisted @Classified * *
Matches a field with an annotation @Persisted and
an annotation @Classified.
A MethodPattern is of the form
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)*
Note: compared to the previous version, this definition of MethodPattern does
not allow parameter annotation matching (only matching on annotations of parameter types).
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:
- @Oneway * *(..)
Matches a method with any return type and any name, that has an annotation of
type @Oneway.
- @Transaction * (@Persistent org.xyz..*).*(..)
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.
- * *.*(@Immutable *,..)
Matches any method taking at least one parameter, where the parameter
type has an annotation @Immutable.
- within(@Secure *)
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 ')'.
- staticinitialization(@Persistent *)
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 ')'.
- call(@Oneway * *(..))
Matches a call to a method with a @Oneway annotation.
- execution(public (@Immutable *) org.xyz..*.*(..)
The execution of any public method in a package with prefix
org.xyz, where the method returns an
immutable result.
- set(@Cachable * *)
Matches the set of any cachable field.
- handler(!@Catastrophic *)
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 ')'.
Runtime type matching and context exposure
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:
- @this(@Foo)
Matches any join point where the object currently bound to 'this'
has an annotation of type Foo.
- call(* *(..)) && @target(@Classified)
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 == Tx.Policy.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);
Note: an alternative design would be to allow both annotation
patterns and type patterns to be specified in the existing args pcd.
This works well for matching, but is more awkward when it comes to
exposing context.
Access to AnnotatedElement information is 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();
Note: it would be nicer to provide direct helper methods in
the JoinPoint interface or a sub-interface that provide the annotations
directly, something like "AnnotatedElement getThisAnnotationInfo()".
The problem here is that the "AnnotatedElement" type is only in the
Java 5 runtime libraries, and we don't want to tie the AspectJ runtime
library to Java 5. A sub-interface and downcast solution could be used
if these helpers were felt to be sufficiently important.
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:
- @within(@Foo)
Matches any join point where the executing code is defined
within a type which has an annotation of type Foo.
- pointcut insideCriticalMethod(Critical c) :
@withincode(c);
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 MemberSignature
interface is extended with the additional operation
java.lang.reflect.AccessibleObject getAccessibleObject(). 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 MemberSignature) {
// this must be an initialization, pre-initialization, call, execution, get, or
// set join point.
AnnotatedElement memberAnnotationInfo = ((MemberSignature)sig).getAccessibleObject();
}
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.
Package and Parameter Annotations
Note: A previous design allowed package annotation patterns to be specified
directly in type patterns, and parameter annotation patterns to be
specified directly in method and constructor signature patterns. Because
this made some pointcut expressions hard to read and understand, we moved
in favour of the design presented below, which also has its drawbacks.
Matching on package and parameter annotations will be
deferred until after the 1.5.0 release so that we can gain more understanding
of the kinds of uses AspectJ users are making of annotations in pointcut
expressions before commiting to any one approach.
Annotation Inheritance and pointcut matching
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().
It would be useful to be able to match join points based on annotation
values, rather than merely the presence of a class-file retention
annotation of a given type. This facility may be supported in a future
version of AspectJ, by expanding the definition of AnnotationPattern. Matching annotation values for
annotations with runtime retention can be done by exposing the annotation value
as a pointcut parameter and then using an if pointcut expression
to test the value.