156

I want to monitor all public methods of all Classes with specified annotation (say @Monitor) (note: Annotation is at class level). What could be a possible pointcut for this? Note: I am using @AspectJ style Spring AOP.

Rejeev Divakaran
  • 4,384
  • 7
  • 36
  • 36
  • The below one works to an extend. @Pointcut("execution(* (@org.rejeev.Monitor *).*(..))") However now the advice is being executed twice. Any clue? – Rejeev Divakaran Jan 06 '10 at 08:32
  • Another point is that the @Monitor annotation is on an interface and there a class implements that. Does the presence of a interface and class will cause double execution of such advice? – Rejeev Divakaran Jan 06 '10 at 12:18
  • 10
    You should accept the excellent answer below. This gives him reputation. There are precious few people here on SO who can answer AspectJ questions. – fool4jesus Nov 12 '13 at 17:38

10 Answers10

206

You should combine a type pointcut with a method pointcut.

These pointcuts will do the work to find all public methods inside a class marked with an @Monitor annotation:

@Pointcut("within(@org.rejeev.Monitor *)")
public void beanAnnotatedWithMonitor() {}

@Pointcut("execution(public * *(..))")
public void publicMethod() {}

@Pointcut("publicMethod() && beanAnnotatedWithMonitor()")
public void publicMethodInsideAClassMarkedWithAtMonitor() {}

Advice the last pointcut that combines the first two and you're done!

If you're interested, I have written a cheat sheet with @AspectJ style here with a corresponding example document here.

Espen
  • 10,545
  • 5
  • 33
  • 39
  • 1
    Thanks. The discussion of Annotation pointcuts on your Cheat Sheet is particularly useful. – GregHNZ Feb 07 '13 at 22:11
  • 1
    How do I get reference to the class in the advice the way I do with normal pointcut advices is @Before("onObjectAction() && this(obj)") – Priyadarshi Kunal Jul 29 '13 at 07:35
  • The Cheat Sheet was very helpful, even though it's been 5 years :) – Yadu Krishnan Feb 19 '15 at 13:16
  • Just a question here, if two methods that are in hierarchy and both fall under the pointcut and belong to same class, will this execute on both? If Yes, then see http://stackoverflow.com/questions/37583539/aspectj-before-not-logging-two-methods-from-same-class, because this is not happening in my case. – HVT7 Jun 02 '16 at 06:18
  • I feel execution public is redundant because you can't have a pointcut on private methods – amstegraf Apr 24 '20 at 14:32
78

Using annotations, as described in the question.

Annotation: @Monitor

Annotation on class, app/PagesController.java:

package app;
@Controller
@Monitor
public class PagesController {
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody String home() {
        return "w00t!";
    }
}

Annotation on method, app/PagesController.java:

package app;
@Controller
public class PagesController {
    @Monitor
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public @ResponseBody String home() {
        return "w00t!";
    }
}

Custom annotation, app/Monitor.java:

package app;
@Component
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Monitor {
}

Aspect for annotation, app/MonitorAspect.java:

package app;
@Component
@Aspect
public class MonitorAspect {
    @Before(value = "@within(app.Monitor) || @annotation(app.Monitor)")
    public void before(JoinPoint joinPoint) throws Throwable {
        LogFactory.getLog(MonitorAspect.class).info("monitor.before, class: " + joinPoint.getSignature().getDeclaringType().getSimpleName() + ", method: " + joinPoint.getSignature().getName());
    }

    @After(value = "@within(app.Monitor) || @annotation(app.Monitor)")
    public void after(JoinPoint joinPoint) throws Throwable {
        LogFactory.getLog(MonitorAspect.class).info("monitor.after, class: " + joinPoint.getSignature().getDeclaringType().getSimpleName() + ", method: " + joinPoint.getSignature().getName());
    }
}

Enable AspectJ, servlet-context.xml:

<aop:aspectj-autoproxy />

Include AspectJ libraries, pom.xml:

<artifactId>spring-aop</artifactId>
<artifactId>aspectjrt</artifactId>
<artifactId>aspectjweaver</artifactId>
<artifactId>cglib</artifactId>
Alex
  • 5,909
  • 2
  • 35
  • 25
  • 1
    Nice example. One question: why does the Annotation `Monitor` have to be a Spring `Component`? – mwhs Nov 22 '13 at 16:36
  • 1
    The `Component` annotation is used to tell the Spring container to apply include the class in the AspectJ weaver thing. By default, Spring only looks at `Controller`, `Service`, and other specific annotations, but not `Aspect`. – Alex Nov 22 '13 at 20:46
  • 1
    Ok Thanks. But I was talking about the `@Component` annotation on the `@interface` not the `Aspect`. Why is that needed? – mwhs Nov 23 '13 at 19:18
  • 2
    The `@Component` annotation makes it so Spring will compile it with the AspectJ IoC/DI aspect-oriented system. I don't know how to say it differently. http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/aop.html – Alex Nov 25 '13 at 01:39
  • Does this do only "public" methods in the annotated classes or does it do all the methods (no matter what access level)? – Lee Meador Apr 20 '15 at 20:25
  • I am pretty sure you can intercept private methods as well. What really happens is that AspectJ rewrites the class. – Alex Apr 23 '15 at 12:57
16

Something like that:

@Before("execution(* com.yourpackage..*.*(..))")
public void monitor(JoinPoint jp) {
    if (jp.getTarget().getClass().isAnnotationPresent(Monitor.class)) {
       // perform the monitoring actions
    }
}

Note that you must not have any other advice on the same class before this one, because the annotations will be lost after proxying.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
15

Use

@Before("execution(* (@YourAnnotationAtClassLevel *).*(..))")
    public void beforeYourAnnotation(JoinPoint proceedingJoinPoint) throws Throwable {
}
Davide Consonni
  • 2,094
  • 26
  • 27
6

it should be enough to mark your aspect method like this:

@After("@annotation(com.marcot.CommitTransaction)")
    public void after() {

have a look at this for a step by step guide on this.

marcocast
  • 401
  • 4
  • 9
3

You can also define the pointcut as

public pointcut publicMethodInsideAClassMarkedWithAtMonitor() : execution(public * (@Monitor *).*(..));
Shekhar
  • 5,771
  • 10
  • 42
  • 48
3

I share with you a code that can be useful, it is to create an annotation that can be used either in a class or a method.

@Target({TYPE, METHOD})
@Retention(RUNTIME)
@Documented
public @interface AnnotationLogger {
    /**
     * It is the parameter is to show arguments in the method or the class.
     */
    boolean showArguments() default false;
}


@Aspect
@Component
public class AnnotationLoggerAspect {
    
    @Autowired 
    private Logger logger;  
    
    private static final String METHOD_NAME   = "METHOD NAME: {} ";
    private static final String ARGUMENTS     = "ARGS: {} ";
    
    @Before(value = "@within(com.org.example.annotations.AnnotationLogger) || @annotation(com.org.example.annotations.AnnotationLogger)")
    public void logAdviceExecutionBefore(JoinPoint joinPoint){  
        CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
        AnnotationLogger annotationLogger = getAnnotationLogger(joinPoint);
        if(annotationLogger!= null) {
            StringBuilder annotationLoggerFormat = new StringBuilder();
            List<Object> annotationLoggerArguments = new ArrayList<>();
            annotationLoggerFormat.append(METHOD_NAME);
            annotationLoggerArguments.add(codeSignature.getName());
            
            if (annotationLogger.showArguments()) {
                annotationLoggerFormat.append(ARGUMENTS);
                List<?> argumentList = Arrays.asList(joinPoint.getArgs());
                annotationLoggerArguments.add(argumentList.toString());
            }
            logger.error(annotationLoggerFormat.toString(), annotationLoggerArguments.toArray());
        }
    }
    
    private AnnotationLogger getAnnotationLogger(JoinPoint joinPoint) {
        AnnotationLogger annotationLogger = null;
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = joinPoint.getTarget().getClass().
                    getMethod(signature.getMethod().getName(), signature.getMethod().getParameterTypes());
            
            if (method.isAnnotationPresent(AnnotationLogger.class)){
                annotationLogger = method.getAnnotation(AnnotationLoggerAspect.class);
            }else if (joinPoint.getTarget().getClass().isAnnotationPresent(AnnotationLoggerAspect.class)){
                annotationLogger = joinPoint.getTarget().getClass().getAnnotation(AnnotationLoggerAspect.class);
            }
            return annotationLogger;
        }catch(Exception e) {
            return annotationLogger;
        }
    }
}
J. Abel
  • 890
  • 3
  • 17
  • 38
3

The simplest way seems to be :

@Around("execution(@MyHandling * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
   throws Throwable {
   // perform actions before

   return pjp.proceed();

   // perform actions after
}

It will intercept execution of all methods specifically annotated with '@MyHandling' in 'YourService' class. To intercept all methods without exception, just put the annotation directly on the class.

No matter of the private / public scope here, but keep in mind that spring-aop cannot use aspect for method calls in same instance (typically private ones), because it doesn't use the proxy class in this case.

We use @Around advice here, but it's basically the same syntax with @Before, @After or any advice.

By the way, @MyHandling annotation must be configured like this :

@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.TYPE })
public @interface MyHandling {

}
Donatello
  • 3,486
  • 3
  • 32
  • 38
  • that is not answering the original statement, with ElementType.Type – Alex May 15 '12 at 00:07
  • yes, ElementType.TYPE will also allow to put annotation directly on classes, which I suppose, will result to handle any method in this class. Am i true ? Is it really working ? – Donatello May 24 '12 at 12:26
  • The `// perform actions after` will never get called since we're returning the value in the line before. – josephpconley Apr 21 '17 at 15:34
2

From Spring's AnnotationTransactionAspect:

/**
 * Matches the execution of any public method in a type with the Transactional
 * annotation, or any subtype of a type with the Transactional annotation.
 */
private pointcut executionOfAnyPublicMethodInAtTransactionalType() :
    execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *);
xmedeko
  • 7,336
  • 6
  • 55
  • 85
1

You could use Spring's PerformanceMonitoringInterceptor and programmatically register the advice using a beanpostprocessor.

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Monitorable
{

}


public class PerformanceMonitorBeanPostProcessor extends ProxyConfig implements BeanPostProcessor, BeanClassLoaderAware, Ordered,
    InitializingBean
{

  private Class<? extends Annotation> annotationType = Monitorable.class;

  private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

  private Advisor advisor;

  public void setBeanClassLoader(ClassLoader classLoader)
  {
    this.beanClassLoader = classLoader;
  }

  public int getOrder()
  {
    return LOWEST_PRECEDENCE;
  }

  public void afterPropertiesSet()
  {
    Pointcut pointcut = new AnnotationMatchingPointcut(this.annotationType, true);
    Advice advice = getInterceptor();
    this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
  }

  private Advice getInterceptor()
  {
    return new PerformanceMonitoringInterceptor();
  }

  public Object postProcessBeforeInitialization(Object bean, String beanName)
  {
    return bean;
  }

  public Object postProcessAfterInitialization(Object bean, String beanName)
  {
    if(bean instanceof AopInfrastructureBean)
    {
      return bean;
    }
    Class<?> targetClass = AopUtils.getTargetClass(bean);
    if(AopUtils.canApply(this.advisor, targetClass))
    {
      if(bean instanceof Advised)
      {
        ((Advised)bean).addAdvisor(this.advisor);
        return bean;
      }
      else
      {
        ProxyFactory proxyFactory = new ProxyFactory(bean);
        proxyFactory.copyFrom(this);
        proxyFactory.addAdvisor(this.advisor);
        return proxyFactory.getProxy(this.beanClassLoader);
      }
    }
    else
    {
      return bean;
    }
  }
}
Vikram
  • 21
  • 1