12

I am fiddling around with AspectJ and came up with an idea that I don't seem to be able to implement properly (story of my life).

I have defined an aspect :

package my.package;

import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class MyAspect {

    @Pointcut("execution(* *(..)) && this(o)")
    public void instanceMethod(Object o) {}

    @Pointcut("within(@Marker *)")
    public void methodsFromMarkedClasses() {}

    @Around("methodsFromMarkedClasses() && instanceMethod(o)")
    public Object markedMethodsAdvice(ProceedingJoinPoint joinPoint, Object o) throws Throwable {
        // do awesome stuff
        return null; //<- not the actual return, just added this so that my head wouldn't hurt
    }
}

I have defined @Marker annotation which is just empty.

The idea is to have the advice markedMethodsAdvice execute ANY time a method is called on an object of a class marked with @Marker. Even (and here're the tricky parts) :


Case 1

If said method is inherited from a class that is not marked see example :

Given

package my.package;
public class Alpha {
    public void methodA() {}
}

When

package my.package;
@Marked
public class Beta extends Alpha {}

Then

/* Should trigger the advice */
new Beta().methodA();

Case 2

If said method is called on an object of a subclass (Liskov)

Given

@Marked
public class Beta extends Alpha { //still extends A just to be coherent with previous example
    public void methodB() {}
}

When

package my.package;
public class Gamma extends Beta {
}

Then

/* Should trigger the advice */
new Gamma().methodB();

(And since I'm greedy I ask this bonus one)

Case 3

If said method is declared on a subclass

Given

@Marked
public class Beta extends Alpha {} //still extends A just to be coherent with previous example

When

package my.package;
public class Gamma extends Beta {
    public void methodC() {}
}

Then

/* Should trigger the advice */
new Gamma().methodC();

The aspectj documentation seems to state that this is possible

execution(public void Middle.*())

picks out all method executions for public methods returning void and having no arguments that are either declared in, or inherited by, Middle, even if those methods are overridden in a subclass of Middle

So what did I do wrong ?

Ar3s
  • 2,237
  • 2
  • 25
  • 47
  • You should note, that annotations in java not inherited, so Gamma in your examples doesn't have annotation `@Marked`. Also, should read second section of this - http://www.eclipse.org/aspectj/doc/released/progguide/apcs04.html – Alexander Kudrevatykh Jun 14 '15 at 10:35
  • Regarding the non inheritance of annotations i got it (thanks to @jlvaquero and even though it still puzzles me as of how aspectJ really does it's inheritance checking because it seem that you can declare `"within sanyone that inherits from a class respecting this set of conditions (including annotations)"`) but I don't really get what you mean with your link. I read it but I don't fully understand what you try to show me. – Ar3s Jun 15 '15 at 13:27
  • I want to show you `declaring/target code must be under the control of ajc`, may be I'm mistaken, but as I understand user can use your annotations without ajc. – Alexander Kudrevatykh Jun 15 '15 at 15:14
  • No they can not ;) my developers can use this annotation within the project where the aspect is declared. They may also use it on other project but they are advised that they need to weave the aspect jar along. I don't expect it to magically work by the grace of anything funny ;p – Ar3s Jun 16 '15 at 09:45

2 Answers2

1

You could try to annotate @Marker annotation with @Inherited annotation. Subclasses should inherit @Marker annotation.

java.lang.annotation.Inherited

@Inherited
public @interface Marked{

}
jlvaquero
  • 8,571
  • 1
  • 29
  • 45
  • Thanks but that only solves the part of the problem I was less concerned about :/ With this I'll "mark" the methods declared both in `Beta` and `Gamma`. But I don't "mark" methods declared in `Alpha` WHEN they are used on `Beta` instances (or any of it's offsprings). – Ar3s Jun 15 '15 at 13:23
  • Are you sure? Is tested? – jlvaquero Jun 15 '15 at 14:50
-1

I can only think of writing a few lines of code in the advice body to perform your 3 cases.

public aspect MyAspect {

    pointcut instanceMethod(Alpha o) : execution(* Alpha+.*(..)) && this(o);

    Object around(Alpha o):  instanceMethod(o){
        Class clazz = o.getClass();
        this.findSpecificAnnotation(clazz);     
        return null; //<- not the actual return, just added this so that my head wouldn't hurt
    }

    private void findSpecificAnnotation (Class c){

        boolean exists = Arrays.asList(c.getAnnotations()).stream().anyMatch(an -> {
            if (an.annotationType().getSimpleName().equals("Marked")){
                 // do awesome stuff
                System.out.println("Around" );
                return true;
            }
            else 
                return false;
        });

        if (!exists && !c.getSimpleName().equals("Object")) {
            this.findSpecificAnnotation(c.getSuperclass());
        }

    }
}

This simply recursively is looking exposed classes and their subclasses so as to find whether they have an annotation which is Marker or not. You can ,of course, improve the code as you want.

Hakan Özler
  • 968
  • 1
  • 10
  • 22
  • The problem with that solution is that I'd have to know from which class 'A' my marked class 'B' will inherit when I design my aspect (which I don't) so I can take said class A as my starting point. 'A' would have to be woven with MyAspect. But I am in a design agnostic context. I don't know beforehand on which classes the annotation Marked will be used and which classes they will extend. For instance a user might use 'Marked' on a class that extends 'HttpServletRequestWrapper'. But that I don't and will never know at the time I write my aspect. – Ar3s Jun 09 '15 at 09:36
  • if I understand you @Ar3s correctly, I believe you can still do what you say after setting the pointcut like `(Object o): execution(* *(..)) && this(o) && !within(MyAspect)` . now you need to refactor the given code for your purpose. I hope so. – Hakan Özler Jun 09 '15 at 13:09
  • Doing so I can still only intercept calls of methods defined in classes that have been compiled along with my aspect. My example of "HttpServletRequestWrapper" would not work. – Ar3s Jun 09 '15 at 13:15