@Aspectj Pointcut for All Methods of a Class with Specific Annotation

How to define a Pointcut to pick out all methods invoked by the specified method?

I think we are not talking about inheritance, so you should not call it a "parent method". You simply mean a method calling other methods, do you not? Anyway, concerning your question:

With Spring AOP you have limited means of expressing control flow pointcuts, compared to native AspectJ's cflow() and cflowbelow() pointcuts. In your case and if you want to stick with proxy-based Spring AOP, a ControlFlowPointcut might be enough, because you do not need any method name patterns but seem to have a fixed method name as your target. For more information, see:

  • Section "update 2" of this answer for basic pointers to resources concerning ControlFlowPointcut.
  • This answer, if you want to match method patterns with a custom MultiMethodControlFlowPointcut (currently still unsupported by Spring out of the box).
  • Spring manual chapter "Using AspectJ with Spring Applications" explains how to configure Spring to use native AspectJ via load-time weaving (LTW).
  • If you decide to go the native AspectJ LTW way, the AspectJ manual section about control-flow-based pointcuts briefly explains cflow and cflowbelow.

in aspectj, can we define handler together with certain annotation?

Interesting question. There are several problems in your aspect code:

In your GitHub repository you use @within(x.y.CollectException). That would intercept joinpoints in annotated classes, but your example class has an annotated method, the class itself is not annotated. Therefore, that pointcut would never match.

In your sample code here, you had the right idea to use @annotation(x.y.CollectException) in order to intercept annotated methods. You even added && execution(* (..)) so as to limit matching to execution pointcuts and exclude call ones. Otherwise the pointcut would fire twice per method in AspectJ (not in Spring AOP where there are not call joinpoints). So far, so good.

But just like call and execution are not the same and therefore mutually exclusive, so are execution and handler. A handler joinpoint is usually somewhere inside (the control flow of) a method execution, but it is not the method execution. This is also your clue to the solution. You actually want to limit matching to exception handlers in the control flow of an annotated method:

@Pointcut("@annotation(de.scrum_master.common.CollectException) && execution(* *(..))")
public void annotationPointCutDefinition() { }

@Before("cflow(annotationPointCutDefinition()) && handler(*) && args(e)")
public void logCaughtException(JoinPoint thisJoinPoint, Throwable e) {
System.out.println(thisJoinPoint + " -> " + e.getClass().getCanonicalName());
}

In your GitHub example, you will see the following line in the console log:

handler(catch(ArithmeticException)) -> java.lang.ArithmeticException

Intercept annotated classes and methods in Spring AOP or AspectJ

Here is an AspectJ example. The pointcut syntax is the same in Spring AOP.

Helper classes:

package de.scrum_master.app;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Intercepted {}
package de.scrum_master.app;

@Intercepted
public class AnnotatedClass {
public void doSomething() {}
public void doSomethingElse() {}
}
package de.scrum_master.app;

public class AnnotatedMethod {
@Intercepted
public void doSomething() {}
public void doSomethingElse() {}
}
package de.scrum_master.app;

@Intercepted
public class AnnotatedMixed {
@Intercepted
public void doSomething() {}
public void doSomethingElse() {}
}

Driver application (Java SE, no Spring):

package de.scrum_master.app;

public class Application {
public static void main(String[] args) {
// Should be logged
new AnnotatedClass().doSomething();
// Should be logged
new AnnotatedClass().doSomethingElse();

// Should be logged
new AnnotatedMethod().doSomething();
// Should NOT be logged
new AnnotatedMethod().doSomethingElse();

// Should be logged, but only once
new AnnotatedMixed().doSomething();
// Should be logged
new AnnotatedMixed().doSomethingElse();
}
}

Aspect:

Please note that the execution(* *(..)) && part is not necessary in Spring AOP because only method execution joinpoints are supported there. The pointcut could just be annotatedMethod() || annotatedClass() there. In AspectJ I have to be more precise because otherwise other joinpoint types would be logged.

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AnnotationInterceptor {
@Pointcut("@annotation(de.scrum_master.app.Intercepted)")
public void annotatedMethod() {}

@Pointcut("@within(de.scrum_master.app.Intercepted)")
public void annotatedClass() {}

@Before("execution(* *(..)) && (annotatedMethod() || annotatedClass())")
public void log(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}

Console log:

execution(void de.scrum_master.app.AnnotatedClass.doSomething())
execution(void de.scrum_master.app.AnnotatedClass.doSomethingElse())
execution(void de.scrum_master.app.AnnotatedMethod.doSomething())
execution(void de.scrum_master.app.AnnotatedMixed.doSomething())
execution(void de.scrum_master.app.AnnotatedMixed.doSomethingElse())

@AspectJ pointcut for all methods inside package

How about one of these alternatives?

A) General execution pointcut with package restrictions:

execution(* *(..)) &&
(
within(com.abc.xyz..controller..*) ||
within(com.abc.xyz..service..*) ||
within(com.abc.xyz..dao..*)
)

B) Package-restricted execution pointcuts:

execution(* com.abc.xyz..controller..*(..)) ||
execution(* com.abc.xyz..service..*(..)) ||
execution(* com.abc.xyz..dao..*(..))

I prefer B, by the way, just because it is a bit shorter and easier to read. As you have probably guessed, the .. notation means "any package or subpackage", whereas * at the end of the expression after .. means "any method in any class".

Spring AOP pointcut for all public methods of an annotatted class (including parent class methods)

Following pointcut expression will intercept the parent methods as well

From the documentation

@Pointcut("within(com.app..*) && execution(public * com.app..*.*(..))")
public void publicMethodsInApp() {
}

@Around("(publicMethodsInApp() && @target(MyAnnotation)) || "
+ "(publicMethodsInApp() && @annotation(MyAnnotation))")
public Object myWrapper(ProceedingJoinPoint invocation) throws Throwable {
//..
}

@target: Limits matching to join points (the execution of methods when
using Spring AOP) where the class of the executing object has an
annotation of the given type.

Aspectj pointcut method with annotation in subclass

Given this annotation:

package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}

Let us assume we have three classes:

  • a parent class without annotation,
  • a plain child class without annotation and
  • an annotated child class:
package de.scrum_master.app;

public class Parent {
public void doSomething() {}
}
package de.scrum_master.app;

public class PlainChild extends Parent {
int doSomethingElse() { return 11; }
}
package de.scrum_master.app;

@MyAnnotation
public class AnnotatedChild extends Parent {
String doSomethingSpecial(int number) { return ""; }
}

Here is a little driver application instantiating all three classes, calling all available methods on them, inherited or not, with different signatures and return types:

package de.scrum_master.app;

public class Application {
public static void main(String[] args) {
new Parent().doSomething();
new PlainChild().doSomething();
new PlainChild().doSomethingElse();
new AnnotatedChild().doSomething();
new AnnotatedChild().doSomethingSpecial(123);
}
}

Finally, here is the aspect doing what was asked in the question: It intercepts all method executions in Parent or any of its subclasses (thus the +), but only if the class of the current instance this bears @MyAnnotation:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
@Around(
"execution(* de.scrum_master.app.Parent+.*(..)) && " +
"@this(de.scrum_master.app.MyAnnotation)"
)
public Object myAdvice(ProceedingJoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
System.out.println(" " + thisJoinPoint.getThis());
return thisJoinPoint.proceed();
}
}

The console log:

execution(void de.scrum_master.app.Parent.doSomething())
de.scrum_master.app.AnnotatedChild@681a9515
execution(String de.scrum_master.app.AnnotatedChild.doSomethingSpecial(int))
de.scrum_master.app.AnnotatedChild@13221655

As you can see, doSomething() is called three times, but only intercepted once. You can also see from the printed getThis() object, that really the right execution is intercepted.



Related Topics



Leave a reply



Submit