Advice declarations change the behavior of classes they crosscut, but do
not change their static type structure. For crosscutting concerns that do
operate over the static structure of type hierarchies, AspectJ provides
inter-type member declarations and other declare
forms.
AspectJ allows the declaration of members by aspects that are associated with other types.
An inter-type method declaration looks like
[ Modifiers
]
Type
OnType
.
Id
(Formals
)
[ ThrowsClause
]
{ Body
}
abstract
[ Modifiers
]
Type
OnType
. Id
(Formals
)
[ ThrowsClause
]
;
The effect of such a declaration is to make OnType
support the new method. Even if OnType
is
an interface. Even if the method is neither public nor abstract. So the
following is legal AspectJ code:
interface Iface {} aspect A { private void Iface.m() { System.err.println("I'm a private method on an interface"); } void worksOnI(Iface iface) { // calling a private method on an interface iface.m(); } }
An inter-type constructor declaration looks like
[ Modifiers
]
OnType
. new (
Formals
)
[ ThrowsClause
]
{ Body
}
The effect of such a declaration is to make
OnType
support the new constructor. It is
an error for OnType
to be an interface.
Inter-type declared constructors cannot be used to assign a
value to a final variable declared in OnType
.
This limitation significantly increases the ability to both understand
and compile the OnType
class and the
declaring aspect separately.
Note that in the Java language, classes that define no constructors
have an implicit no-argument constructor that just calls
super()
. This means that attempting to declare
a no-argument inter-type constructor on such a class may result in
a conflict, even though it looks like no
constructor is defined.
An inter-type field declaration looks like one of
[ Modifiers
]
Type
OnType
. Id
= Expression
;
[ Modifiers
]
Type
OnType
. Id
;
The effect of such a declaration is to make
OnType
support the new field. Even if
OnType
is an interface. Even if the field is
neither public, nor static, nor final.
The initializer, if any, of an inter-type field declaration runs before the class-local initializers defined in its target class.
Any occurrence of the identifier this
in the body of
an inter-type constructor or method declaration, or in the initializer
of an inter-type field declaration, refers to the
OnType
object rather than to the aspect
type; it is an error to access this
in such a
position from a static
inter-type member
declaration.
Inter-type member declarations may be public or private, or have default (package-protected) visibility. AspectJ does not provide protected inter-type members.
The access modifier applies in relation to the aspect, not in relation to the target type. So a private inter-type member is visible only from code that is defined within the declaring aspect. A default-visibility inter-type member is visible only from code that is defined within the declaring aspect's package.
Note that a declaring a private inter-type method (which AspectJ
supports) is very different from inserting a private method declaration
into another class. The former allows access only from the declaring
aspect, while the latter would allow access only from the target type.
Java serialization, for example, uses the presense of a private method
void writeObject(ObjectOutputStream)
for the
implementation of java.io.Serializable
. A private
inter-type declaration of that method would not fulfill this
requirement, since it would be private to the aspect, not private to
the target type.
The access modifier of abstract inter-type methods has one constraint: It is illegal to declare an abstract non-public inter-type method on a public interface. This is illegal because it would say that a public interface has a constraint that only non-public implementors must fulfill. This would not be compatible with Java's type system.
Inter-type declarations raise the possibility of conflicts among
locally declared members and inter-type members. For example, assuming
otherPackage
is not the package containing the
aspect A
, the code
aspect A { private Registry otherPackage.onType.r; public void otherPackage.onType.register(Registry r) { r.register(this); this.r = r; } }
declares that onType
in otherPackage
has a field
r
. This field, however, is only accessible from the
code inside of aspect A
. The aspect also declares
that onType
has a method
"register
", but makes this method accessible from
everywhere.
If onType
already defines a
private or package-protected field "r
", there is no
conflict: The aspect cannot see such a field, and no code in
otherPackage
can see the inter-type
"r
".
If onType
defines a public field
"r
", there is a conflict: The expression
this.r = r
is an error, since it is ambiguous whether the private inter-type
"r
" or the public locally-defined
"r
" should be used.
If onType
defines a method
"register(Registry)
" there is a conflict, since it
would be ambiguous to any code that could see such a defined method
which "register(Registry)
" method was applicable.
Conflicts are resolved as much as possible as per Java's conflict resolution rules:
Given a potential conflict between inter-type member declarations in
different aspects, if one aspect has precedence over the other its
declaration will take effect without any conflict notice from compiler.
This is true both when the precedence is declared explicitly with
declare precedence
as well as when when sub-aspects
implicitly have precedence over their super-aspect.
An aspect may change the inheritance hierarchy of a system by changing
the superclass of a type or adding a superinterface onto a type, with
the declare parents
form.
declare parents: TypePattern
extends Type
;
declare parents: TypePattern
implements TypeList
;
For example, if an aspect wished to make a particular class runnable,
it might define appropriate inter-type void
run()
method, but it should also declare that the class
fulfills the Runnable
interface. In order to
implement the methods in the Runnable
interface, the
inter-type run()
method must be public:
aspect A { declare parents: SomeClass implements Runnable; public void SomeClass.run() { ... } }
Through the use of inter-type members, interfaces may now carry (non-public-static-final) fields and (non-public-abstract) methods that classes can inherit. Conflicts may occur from ambiguously inheriting members from a superclass and multiple superinterfaces.
Because interfaces may carry non-static initializers, each interface
behaves as if it has a zero-argument constructor containing its
initializers. The order of super-interface instantiation is
observable. We fix this order with the following properties: A
supertype is initialized before a subtype, initialized code runs only
once, and the initializers for a type's superclass are run before the
initializers for its superinterfaces. Consider the following hierarchy
where {Object
, C
,
D
, E
} are classes,
{M
, N
, O
,
P
, Q
} are interfaces.
Object M O \ / \ / C N Q \ / / D P \ / E
when a new E
is instantiated, the initializers run in this order:
Object M C O N D Q P E
An aspect may specify that a particular join point should never be reached.
declare error: Pointcut
: String
;
declare warning: Pointcut
: String
;
If the compiler determines that a join point in
Pointcut
could possibly be reached, then it
will signal either an error or warning, as declared, using the
String
for its message.
An aspect may specify that a particular kind of exception, if
thrown at a join point, should bypass Java's usual static exception
checking system and instead be thrown as a
org.aspectj.lang.SoftException
, which is subtype of
RuntimeException
and thus does not need to be
declared.
declare soft: Type
: Pointcut
;
For example, the aspect
aspect A { declare soft: Exception: execution(void main(String[] args)); }
Would, at the execution join point, catch any
Exception
and rethrow a
org.aspectj.lang.SoftException
containing
original exception.
This is similar to what the following advice would do
aspect A { void around() execution(void main(String[] args)) { try { proceed(); } catch (Exception e) { throw new org.aspectj.lang.SoftException(e); } } }
except, in addition to wrapping the exception, it also affects Java's static exception checking mechanism.
Like advice, the declare soft form has no effect in an abstract aspect that is not extended by a concreate aspect. So the following code will not compile unless it is compiled with an extending concrete aspect:
abstract aspect A { abstract pointcut softeningPC(); before() : softeningPC() { Class.forName("FooClass"); // error: uncaught ClassNotFoundException } declare soft : ClassNotFoundException : call(* Class.*(..)); }
An aspect may declare a precedence relationship between concrete
aspects with the declare precedence
form:
declare precedence :
TypePatternList
;
This signifies that if any join point has advice from two
concrete aspects matched by some pattern in
TypePatternList
, then the precedence of
the advice will be the order of in the list.
In TypePatternList
, the wildcard "*" can
appear at most once, and it means "any type not matched by any other
pattern in the list".
For example, the constraints that (1) aspects that have Security as part of their name should have precedence over all other aspects, and (2) the Logging aspect (and any aspect that extends it) should have precedence over all non-security aspects, can be expressed by:
declare precedence: *..*Security*, Logging+, *;
For another example, the CountEntry aspect might want to count the entry to methods in the current package accepting a Type object as its first argument. However, it should count all entries, even those that the aspect DisallowNulls causes to throw exceptions. This can be accomplished by stating that CountEntry has precedence over DisallowNulls. This declaration could be in either aspect, or in another, ordering aspect:
aspect Ordering { declare precedence: CountEntry, DisallowNulls; } aspect DisallowNulls { pointcut allTypeMethods(Type obj): call(* *(..)) && args(obj, ..); before(Type obj): allTypeMethods(obj) { if (obj == null) throw new RuntimeException(); } } aspect CountEntry { pointcut allTypeMethods(Type obj): call(* *(..)) && args(obj, ..); static int count = 0; before(): allTypeMethods(Type) { count++; } }
It is an error for any aspect to be matched by more than one TypePattern in a single decare precedence, so:
declare precedence: A, B, A ; // error
However, multiple declare precedence forms may legally have this kind of circularity. For example, each of these declare precedence is perfectly legal:
declare precedence: B, A; declare precedence: A, B;
And a system in which both constraints are active may also be legal, so long as advice from A and B don't share a join point. So this is an idiom that can be used to enforce that A and B are strongly independent.
Consider the following library aspects:
abstract aspect Logging { abstract pointcut logged(); before(): logged() { System.err.println("thisJoinPoint: " + thisJoinPoint); } } abstract aspect MyProfiling { abstract pointcut profiled(); Object around(): profiled() { long beforeTime = System.currentTimeMillis(); try { return proceed(); } finally { long afterTime = System.currentTimeMillis(); addToProfile(thisJoinPointStaticPart, afterTime - beforeTime); } } abstract void addToProfile( org.aspectj.JoinPoint.StaticPart jp, long elapsed); }
In order to use either aspect, they must be extended with concrete aspects, say, MyLogging and MyProfiling. Because advice only applies from concrete aspects, the declare precedence form only matters when declaring precedence with concrete aspects. So
declare precedence: Logging, Profiling;
has no effect, but both
declare precedence: MyLogging, MyProfiling; declare precedence: Logging+, Profiling+;
are meaningful.
Pointcuts that appear inside of declare
forms
have certain restrictions. Like other pointcuts, these pick out join
points, but they do so in a way that is statically determinable.
Consequently, such pointcuts may not include, directly or indirectly (through user-defined pointcut declarations) pointcuts that discriminate based on dynamic (runtime) context. Therefore, such pointcuts may not be defined in terms of
all of which can discriminate on runtime information.