Reputation: 9266
At the moment, I have the following Pointcut.
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
@Aspect
@Component
public static class MyAnnotationAspect {
@Pointcut("execution(* (@com.test.MyAnnotation *).*(..))")
public void methodInMyAnnotationType() {}
@Around("methodInMyAnnotationType()")
public Object annotate(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP WORKING");
return pjp.proceed();
}
}
}
It's working fine when I add @MyAnnotation
on root level classes as following.
@MyAnnotation
@Service
public class ShiftModule {
@Resource
private ShiftModule self;
/* Executing anything using self.method() triggers the Aspect
* for @MyAnnotation perfectly
*/
}
It's also not working if I add the annotation on an inner static class.
@Service
public class ShiftModule {
@Service
@MyAnnotation
public class AnnotatedShiftModule extends ShiftModule {}
@Resource
private AnnotatedShiftModule self;
/* Executing anything using self.method() does NOT trigger the
* Aspect for @MyAnnotation or even framework's annotations
* like @Async
*/
}
If I use this technique on an interface, it works.
@Repository
public interface OrderRepo extends JpaRepository<Order,Long> {
@Repository("annotatedOrderRepo")
@MyAnnotation
public interface AnnotatedOrderRepo extends OrderRepo {}
}
I'd be very grateful if you could show me how to make it work with classes and Spring beans.
Upvotes: 0
Views: 3349
Reputation: 9266
After digging deeper into the topic of AOP, I finally found a working solution.
Originally, I'm using the following pointcuts.
@Aspect
@Component
public static class MyAnnotationAspect {
/**
* Matches the execution of any methods in a type annotated with @MyAnnotation.
*/
@Pointcut("execution(* (@com.test.MyAnnotation *).*(..))")
public void methodInMyAnnotationType() {}
/**
* Matches the execution of any methods annotated with @MyAnnotation.
*/
@Pointcut("execution(@com.test.MyAnnotation * *.*(..))")
public void methodAnnotatedWithMyAnnotation() {}
@Around("methodInMyAnnotationType() || methodAnnotatedWithMyAnnotation()")
public Object aop(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP IS WORKING");
return pjp.proceed;
}
}
What I learned is that the methodInMyAnnotationType
pointcut will only work if I put @MyAnnotation
on the class that actually owns the method. However, if I put the annotation on class B that extends class A, the AOP cannot intercept methods from class A.
One potential solution I found is as following.
@Pointcut("execution(* *(..)) && @this(com.test.MyAnnotation)")
It means the pointcut is for ALL methods from current class AND parent class and the current class must be annotated with @MyAnnotation
. It looks promising. Unfortunately, Spring AOP doesn't support @this
pointcut primitive which produces UnsupportedPointcutPrimitiveException
.
After a bit more digging into the topic of this
, I found the existence of target
primitive and came up with the following solution.
@Pointcut("execution(@com.test.MyAnnotation * *.*(..))")
public void annotatedMethod() {}
@Pointcut("execution(* (@com.test.MyAnnotation *).*(..))")
public void annotatedClass() {}
@Pointcut("execution(* *(..)) && target(com.test.MyAnnotable)")
public void implementedInterface() {}
@Around("annotatedMethod() || annotatedClass() || implementedInterface()")
public Object aop(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP IS WORKING");
return pjp.proceed;
}
It means the pointcut is for ALL methods from current class AND parent class. In addition, the method must be annotated with @MyAnnotation
or the class containing the method is annotated with @MyAnnotation
or the object that has this method must be an instance of the marker interface MyAnnotable
. It looks nice and it works.
My final class implementation looks like this.
@Service
public class ShiftModule {
@Service
public class Annotated extends ShiftModule implements MyAnnotable {}
@Resource
private ShiftModule.Annotated self;
}
Add-on information:
I did give the following pointcut a try during my experimentation.
@Pointcut("@annotation(com.test.MyAnnotation)")
public void annotatedMethod() {}
@Pointcut("@within(com.test.MyAnnotation)")
public void annotatedClass() {}
@Pointcut("target(com.test.MyAnnotable)")
public void implementedInterface() {}
@Around("execution(* *(..)) && (annotatedMethod() || annotatedClass() || implementedInterface()")
public Object aop(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("AOP IS WORKING");
return pjp.proceed;
}
What I found is it does NOT work with annotated inner interface, meaning the code below will stop working. The AOP aspect doesn't have any effects at all.
@Repository
public interface OrderRepo extends JpaRepository<Order,Long> {
@Repository("annotatedOrderRepo")
@MyAnnotation
public interface Annotated extends OrderRepo {}
}
Upvotes: 2
Reputation: 67317
This is not an answer, but comments are too limited to say what I want to say. This is actually feedback to the OP's own answer:
execution(* (@com.test.MyAnnotation *).*(..))
can also be written more readably as @within(com.test.MyAnnotation)
in Spring AOP because Spring AOP only knows execution joinpoints anyway. In AspectJ you would add && execution(* *(..))
to the pointcut.
execution(@com.test.MyAnnotation * *.*(..))
can also be written more readably as @annotation(com.test.MyAnnotation)
in Spring AOP because Spring AOP only knows execution joinpoints anyway. In AspectJ you would add && execution(* *(..))
to the pointcut.
What I learned is that the
methodInMyAnnotationType
pointcut will only work if I put@MyAnnotation
on the class that actually owns the method.
Of course, because this is a general limitation of Java annotations. They are never inherited to subclasses, from interfaces to classes or methods or from parent class methods to overwritten subclass methods. The only exception is if you use @Inherited
as a meta annotation for annotation type itself, then it gets inherited by subclasses (but again not from interface to implementing class). This is documented here.
As for this()
vs target()
and @this()
vs @target
, as you said the "this" versions are only supported by AspectJ (which you can optionally also use from within a Spring application). The reason is that "this" only makes a difference from "target" in a call()
pointcut where "this" is the calling method and "target" is the called method. Because call()
is also unavailable in Spring AOP, it would not make sense to support the corresponding "this" type pointcuts.
If you are willing to switch to AspectJ, I have a workaround for making implementing classes "inherit" annotations from interfaces and for making specific methods "inherit" annotations too, see this answer.
I am just mentioning all this for educational purposes, not in order to replace your own solution as you seem to be happy with the mix of marker annotations and marker interfaces.
Upvotes: 2