0
votes

Existing answers gives nice explanation on how to use Custom Annotation for method execution time logging. I am wondering if there is way to use same annotation for both Class and Method, but Pointcut should be different where it is used.

@LogExecutionTime
public class MyServiceImpl implements MyService {

    public void run(){
       // logic
    }

    public void walk(){
       // logic
    }

    private void breather(){
       // logic
    }
}

If Annotation is used for class all methods inside class should be considered for Execution Time Logging in Aspect Class (like execution(* com.me.package.MyServiceImpl.*(..))). However if the Annotation is only used for single method inside the class, it is should also consider that only method in Aspect Logging Class. (like execution(* com.you.package.YourServiceImpl.forward(..))).

public class YourServiceImpl implements YourService {

    @LogExecutionTime
    public void forward(){
       // logic
    }

    @LogExecutionTime
    public void backward(){
       // logic
    }

    private void upward(){
       // logic
    }
}

Annotation Class

package com.myproj.core.utils.annotation;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

@Retention(RUNTIME)
public @interface LogExecutionTime {

}

Aspect Class for Annotation (using pointcuts as suggested by @kriegaex)

package com.myproj.core.utils;

import java.time.Duration;

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

import java.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Aspect class to Log Method Execution time
 */
@Aspect
public class MyMethodExecutionLoggingAspect {
    
    private static final Logger LOG = LoggerFactory.getLogger(MyMethodExecutionLoggingAspect.class);
    
    /**
     * This method will log Method Execution Time
     * 
     * @param joinPoint
     * @return object
     * @throws Throwable
     */
    @Around("execution(* (@com.myproj.core.utils.annotation.LogExecutionTime *).*(..)) || execution(@com.myproj.core.utils.annotation.LogExecutionTime * *(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {

        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Instant start = Instant.now();

        try {
            return joinPoint.proceed();
        } finally {

            long end = Duration.between(start, Instant.now()).toMillis();

            if (end > 0) {
                LOG.debug("METHOD EXECUTION TIME LAPSED: {}ms | {}.{}", end, className, methodName);
            }
        }

    }
    
}

Spring Bean Definition in spring.xml

<bean class="com.myproj.core.utils.MyMethodExecutionLoggingAspect" />

1
Have you given up on this? No more feedback? It is still on my follow-up list. - kriegaex
@kriegaex sorry as replied earlier it is not working as expected. I tried to use only class level but somehow it is only logging public methods only. So for now, I am using another approach of directly giving qualified names of methods in @Around. I am definitely going to implement this, but for now it is on hold. Sorry for the trouble. - Zedex7
I am convinced that my answer is correct. The only reason I can think of that it does not work for you is an error in your pointcut. Because the latter is exactly what you hide in your sample code, the question is closed for now. I am sure it will be reopened as soon as you update the code with a a concrete pointcut. I would be happy to further assist if I had something to analyse. - kriegaex
Thanks @kriegaex, even I am sure those pointcuts are correct, but somehow they are not working as expected. FYI I have added full aspect class along with pointcuts as you have provided. - Zedex7
Just to make sure you did not forget anything fundamental: If you use Spring AOP, both the aspect and the target classes must be @Components or declared as @Beans (or more generally, must be Spring-managed) in order to be picked up by Spring AOP. I cannot see anything of the sort in your code. So what does the original code look like? - kriegaex

1 Answers

1
votes

Your sample code with @annotation() does not make sense because you do not specify a method annotation type. It should be something like @annotation(fully.qualified.AnnotationType) instead.

For matching class annotations you want to use @within(fully.qualified.AnnotationType), as described here.

So you could use a pointcut like

@annotation(fully.qualified.AnnotationType)
  || @within(fully.qualified.AnnotationType)

Alternatively, according to my answer here, you could also use the more cryptic version

execution(* (@fully.qualified.AnnotationType *).*(..))
  || execution(@fully.qualified.AnnotationType * *(..))