Inter-type declarations are challenging to support using an annotation style. It's very important to preserve the same semantics between the code style and the annotation style. We also want to support compilation of a large set of @AspectJ applications using a standard Java 5 compiler. For these reasons, the 1.5.0 release of AspectJ 5 only supports inter-type declarations backed by interfaces when using the annotation style - which means it is not possible to introduce constructors or fields, as it would not be not possible to call those unless already woven and available on a binary form.
Consider the following aspect:
public aspect MoodIndicator { public interface Moody {}; private Mood Moody.mood = Mood.HAPPY; public Mood Moody.getMood() { return mood; } declare parents : org.xyz..* implements Moody; before(Moody m) : execution(* *.*(..)) && this(m) { System.out.println("I'm feeling " + m.getMood()); } }
This declares an interface
Moody
, and then makes two
inter-type declarations on the interface - a field that is private to the
aspect, and a method that returns the mood. Within the body of the inter-type
declared method
getMoody
, the type of
this
is
Moody
(the target type of the inter-type declaration).
Using the annotation style this aspect can be written:
@Aspect public class MoodIndicator { // this interface can be outside of the aspect public interface Moody { Mood getMood(); }; // this implementation can be outside of the aspect public static class MoodyImpl implements Moody { private Mood mood = Mood.HAPPY; public Mood getMood() { return mood; } } // the field type must be the introduced interface. It can't be a class. @DeclareParents(value="org.xzy..*",defaultImpl=MoodyImpl.class) private Moody implementedInterface; @Before("execution(* *.*(..)) && this(m)") void feelingMoody(Moody m) { System.out.println("I'm feeling " + m.getMood()); } }
This is very similar to the mixin mechanism supported by AspectWerkz. The
effect of the
@DeclareParents
annotation is equivalent to
a declare parents statement that all types matching the type pattern implement
the given interface (in this case Moody).
Each method declared in the interface is treated as an inter-type declaration.
Note how this scheme operates within the constraints
of Java type checking and ensures that
this
has access
to the exact same set of members as in the code style example.
Note that it is illegal to use the @DeclareParents annotation on an aspect' field of a non-interface type. The interface type is the inter-type declaration contract that dictates which methods are declared on the target type.
// this type will be affected by the inter-type declaration as the type pattern matches package org.xyz; public class MoodTest { public void test() { // see here the cast to the introduced interface (required) Mood mood = ((Moody)this).getMood(); ... } }
The @DeclareParents
annotation can also be used without specifying
a defaultImpl
value (for example,
@DeclareParents("org.xyz..*")
). This is equivalent to a
declare parents ... implements
clause, and does not
make any inter-type declarations for default implementation of the interface methods.
Consider the following aspect:
public aspect SerializableMarker { declare parents : org.xyz..* implements Serializable; }
Using the annotation style this aspect can be written:
@Aspect public class SerializableMarker { @DeclareParents("org.xyz..*") Serializable implementedInterface; }
If the interface defines one or more operations, and these are not implemented by the target type, an error will be issued during weaving.