Inter-type Declarations

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 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")
        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.