Search code examples
javaaopaspectj

AspectJ type pattern for all types with a method with an attribute?


Currently I have the standard one:

@DeclareParents(value = "(@moody.MyAttribute *)", defaultImpl = MoodyImpl.class)

This will add my interface+implementation to any class with @MyAttribute

I would like to do this for all classes that have this attribute AND/OR have a method with that attribute.

So this class should also get my interface+implementation:

class MyClass {
  @MyAttribute
  public void test()
  {
  }
}

Is that possible?


Solution

  • No, because both @DeclareParents and the newer @DeclareMixin need class name specifications in their value parameter. If I were you I would refactor my annotation so as to only be applicable to classes, not methods, and then my code to move all annotations to classes as well.

    One more option if you absolutely want to stay on your path: Since AspectJ 1.8.2 there is a new annotation processing feature. You might want to explore that one and create an annotation processor creating an ITD aspect for each affected class with annotated methods.


    Update: I have just remembered a non-standard compiler option -XhasMember which you can use:

    ajc -X
    
    AspectJ Compiler 1.8.2 non-standard options:
        (...)
        -XhasMember         allow hasmethod() and hasfield type patterns in
                            declare parents and declare @type
        (...)
    

    Caveat: It does not seem to work with @AspectJ syntax, i.e. you cannot use annotation-style @DeclareParents but have to use native AspectJ syntax declare parents. The default interface implementation is also done differently, i.e. via ITD in the aspect, there is no need for a specific implementation class.

    Here is a compileable, fully self-consistent code sample:

    Marker annotation:

    package de.scrum_master.app;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE, ElementType.METHOD })
    public @interface MyAttribute {}
    

    Interface to be implemented via ITD:

    package de.scrum_master.app;
    
    public interface Moody {
        public void sayHelloTo(String name);
    }
    

    Sample classes with(out) marker annotation:

    Class Foo has the annotation at class level, Bar at method level and Zot has no annotation at all.

    package de.scrum_master.app;
    
    @MyAttribute
    public class Foo {
        public static void foo() {}
    }
    
    package de.scrum_master.app;
    
    public class Bar {
        @MyAttribute
        public static void bar() {}
    }
    
    package de.scrum_master.app;
    
    public class Zot {
        public static void zot() {}
    }
    

    Driver application:

    For demonstration purposes, the application checks for the existence of a method sayHelloTo(String) via reflection.

    package de.scrum_master.app;
    
    import java.lang.reflect.Method;
    
    public class Application {
        public static void main(String[] args) throws Exception {
            Method method;
            try {
                method = Foo.class.getDeclaredMethod("sayHelloTo", String.class);
            } catch (NoSuchMethodException nsme) {
                method = null;
            }
            System.out.println("Foo: " + method);
    
            try {
                method = Bar.class.getDeclaredMethod("sayHelloTo", String.class);
            } catch (NoSuchMethodException nsme) {
                method = null;
            }
            System.out.println("Bar: " + method);
    
            try {
                method = Zot.class.getDeclaredMethod("sayHelloTo", String.class);
            } catch (NoSuchMethodException nsme) {
                method = null;
            }
            System.out.println("Zot: " + method);
        }
    }
    

    Console output (without aspect):

    Foo: null
    Bar: null
    Zot: null
    

    Aspect:

    package de.scrum_master.aspect;
    
    import de.scrum_master.app.Moody;
    import de.scrum_master.app.MyAttribute;
    
    public aspect ITDAspect {
        declare parents : @MyAttribute * implements Moody;
        declare parents : hasmethod(@MyAttribute * *(..)) implements Moody;
    
        public void Moody.sayHelloTo(String name) {
            System.out.println("Hello " + name);
        }
    }
    

    Console output (with aspect):

    Foo: public void de.scrum_master.app.Foo.sayHelloTo(java.lang.String)
    Bar: public void de.scrum_master.app.Bar.sayHelloTo(java.lang.String)
    Zot: null
    

    Voilà! We have successfully added the interface including its default implementation to Bar which does not have class-level annotation, but a method-level one.

    Enjoy!