7
 class Test {

@override
public String a(){
b();
d();
}


private String b() {
c();
}

private String c(){
d();
}
private String d(){}

}

I want to intercept each methods of class Test that is been called from overridden method A() and want to know how much time each method like b(), c() took while processing some business logic separately.

How can I achieve it using Spring AOP or Aspectj?

James Z
  • 12,209
  • 10
  • 24
  • 44
Crazy-Coder
  • 107
  • 1
  • 1
  • 9
  • What have you tried? What does your current aspect look like? Which problem are you having with it? – kriegaex Mar 08 '18 at 01:12
  • 1
    @kriegaex I tried to achieve this goal using Spring AOP(wrote@Around for given package) and could be able to intercept method a() successfully but could not find out how to intercept and log the time taken by b(),c() and d() which were the result of code refactoring. I have very less idea of AspectJ and not sure how to do it better way. Kindly help me – Crazy-Coder Mar 08 '18 at 02:59
  • I posted an answer, but one that was not very clear is that, do you want to check also if the method is called by a on runtime? in that case you could intercept the method like mentioned in my answer and then check the stack trace with `Thread.currentThread().getStackTrace()` to see if a() is the caller method. – Jose Da Silva Gomes Mar 08 '18 at 05:43
  • José, this is not the way to go here if that's the requirement. You would use `cflow()` and AspectJ with LTW. The latter you suggested already anyway. But we are speculating. AspectJ can solve all those technical issues, not just self-invocation but also intercepting private methods, both of which Spring AOP cannot. Spring AOP also does not know `cflow()`. The question whether or not the methods should be private or not in the first place is another one. We cannot say if they have fake names like `a()`, `b()` etc. @Crazy-Coder: Have you ever tried an AspectJ tutorial? – kriegaex Mar 08 '18 at 11:29
  • @kriegaex Well neither you or I knows 'what is the requirement', of course aspectj can be used (like I already said) but that may be overkill (or just more complex), if he can use public and the requirement is simple enough there is no need to use LTW. If public methods is not an option, I've just added the possibility to use self injection (which I personally don't like much, but it works and it's pretty simple). Finally the question does not clarify if he in fact needs to know if the method has been executed in a specific function or just want to intercept the methods when executed 'inside'. – Jose Da Silva Gomes Mar 08 '18 at 17:14
  • @kriegaex; @Jose Da Silva Thanks for your kind help, I had never used AspectJ but good understanding of Spring AOP. I can not put actual methods/code here as I am facing such issue while my official development work. Yes there are private methods as mentioned, I have refactored a long method into small private n public methods, and All I want to know which methods( private and public) is taking long time while processing a request. In my question A() is overridden and called from controller rest other mentioned methods are segregated/refactored from A() – Crazy-Coder Mar 08 '18 at 18:31
  • 1
    That this is about your work is not an excuse. Just like I made up an [MCVE](http://stackoverflow.com/help/mcve) in my answer, you could have made one up exactly reflecting your problem without disclosing any work details. You keep us guessing, not saying clearly what is the problem and letting us do **your** work, i.e. making up compilable and executable sample code for you instead of just fixing yours. So kindly accept and upvote an answer. If refactoring into multiple beans and making all methods public or package-scoped is an option, choose José's, otherwise choose mine. – kriegaex Mar 09 '18 at 01:40
  • i think it was already answered here https://stackoverflow.com/questions/21625588/why-spring-aop-is-not-working-for-method-call-inside-another-method – phoenixSid Nov 01 '19 at 11:13

2 Answers2

6

In order to

  • weave into private methods,
  • handle self-invocation within one class,
  • dynamically determine control flow and limit interception to only methods called directly or indirectly by your interface method

you need to switch from Spring AOP (proxy-based, many limitations, slow) to AspectJ using LTW (load-time weaving) as described in the Spring manual.

Here is an example in pure AspectJ (no Spring, Just Java SE) which you can easily adapt to your needs:

Sample interface

package de.scrum_master.app;

public interface TextTransformer {
  String transform(String text);
}

Class implementing interface incl. main method:

As you can see, I made up an example like yours and also made the methods spend time in order to have something to measure in the aspect later:

package de.scrum_master.app;

public class Application implements TextTransformer {
  @Override
  public String transform(String text) {
    String geekSpelling;
    try {
      geekSpelling = toGeekSpelling(text);
      return toUpperCase(geekSpelling);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }

  }

  private String toGeekSpelling(String text) throws InterruptedException {
    Thread.sleep(100);
    return replaceVovels(text).replaceAll("[lL]", "1");
  }

  private String replaceVovels(String text) throws InterruptedException {
    Thread.sleep(75);
    return text.replaceAll("[oO]", "0").replaceAll("[eE]", "Ɛ");
  }

  private String toUpperCase(String text) throws InterruptedException {
    Thread.sleep(50);
    return text.toUpperCase();
  }

  public static void main(String[] args) throws InterruptedException {
    System.out.println(new Application().transform("Hello world!"));
  }
}

Aspect:

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import static java.lang.System.currentTimeMillis;

@Aspect
public class TimingAspect {
  @Around("execution(* *(..)) && cflow(execution(* de.scrum_master.app.TextTransformer.*(..)))")
  public Object measureExecutionTime(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    long startTime = currentTimeMillis();
    Object result = thisJoinPoint.proceed();
    System.out.println(thisJoinPoint + " -> " + (currentTimeMillis() - startTime) + " ms");
    return result;
  }
}

Console log:

execution(String de.scrum_master.app.Application.replaceVovels(String)) -> 75 ms
execution(String de.scrum_master.app.Application.toGeekSpelling(String)) -> 189 ms
execution(String de.scrum_master.app.Application.toUpperCase(String)) -> 63 ms
execution(String de.scrum_master.app.Application.transform(String)) -> 252 ms
HƐ110 W0R1D!

You can also exclude the transform(..) method by just changing the pointcut from cflow() to cflowbelow():

@Around("execution(* *(..)) && cflowbelow(execution(* de.scrum_master.app.TextTransformer.*(..)))")

Then the console log is just:

execution(String de.scrum_master.app.Application.replaceVovels(String)) -> 77 ms
execution(String de.scrum_master.app.Application.toGeekSpelling(String)) -> 179 ms
execution(String de.scrum_master.app.Application.toUpperCase(String)) -> 62 ms
HƐ110 W0R1D!

Incidentally, please do read an AspectJ and/or Spring AOP manual.

halfer
  • 19,824
  • 17
  • 99
  • 186
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks a lot Sir for showing me the way, yes that is what I wanted to know. I will study and implement this concept as per suggested way – Crazy-Coder Mar 08 '18 at 18:37
  • 1
    You are very welcome. But we are both developers and thus peers, regardless of experience level. So please don't call me or other people around here "Sir". :-) – kriegaex Mar 10 '18 at 03:16
  • @ kriegaex getting below exception while using cflow. Kindly help org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException: Pointcut expression 'execution(* (..)) && cflow(execution( com.Org.pts.api.service.impl.CreateServiceImpl.*(..)))' contains unsupported pointcut primitive 'cflow' at org.aspectj.weaver.tools.PointcutParser.validateAgainstSupportedPrimitives(PointcutParser.java:425) [aspectjweaver-1.8.10.jar:1.8.1 – Crazy-Coder Mar 12 '18 at 14:35
  • Please read my answer again, especially the preface. I said explicitly that you need to switch from Spring AOP to AspectJ and even added a link to the Spring manual describing how it needs to be configured. I would really appreciate not having wasted my time writing it up in all that detail. – kriegaex Mar 12 '18 at 15:04
  • @kriegaex is it possible to get the parameters of the top-level method? In the example above, say i define a pointcut to all methods invoked from `a()`, can i somehow access any parameters sent to `a() ` from within the `@Around` advice? – Stefan Rendevski Sep 27 '19 at 08:04
  • 1
    @StefanRendevski, I will be glad to answer your new question. Please include an [MCVE](https://stackoverflow.com/help/mcve) like I did in my answer here, then explain exactly what you want to achieve. Then I can write an answer instead of a comment. First you let your code speak, then I will answer with mine. Hijacking old questions with loosely related follow-up questions inside of comments is not handy for both you and me. – kriegaex Sep 27 '19 at 08:57
2

Spring AOP is applied using proxies, when you call a method of the bean from outside, the proxy is used and the method could be intercepted, but when you call the method from inside the class, the proxy is not used and the class is used directly.


You have three options


The first and easy one, if you do not have problems using public methods is to move the functions b(), c(), and d() to another bean. This way each call to this methods would be intercepted.

public class Test {
    public String a() { ... }
}

public class Test2 {
    public String b() { ... }
    public String c() { ... }
    public String d() { ... }
}

You can also use it as inner static class if you want to keep all in the same file.

public class Test {
    public String a() { ... }
    public static class Test2 {
        public String b() { ... }
        public String c() { ... }
        public String d() { ... }
    }
}

You should autowire Test2 in the constructor of Test.

public class Test {
    private final Test2 test2;    
    @Autowired public Test(final Test2 test2) {
        this.test2 = test2;
    }
    public String a() { 
        test2.b();
        test2.c();
        test2.d();
    }
}

And finally create the around method.

@Around(value = "execution(* package.of.the.class.Test.*(..))")
public Object aroundA(ProceedingJoinPoint pjp) throws Throwable { ... }

@Around(value = "execution(* package.of.the.class.Test2.*(..))")
public Object aroundBCD(ProceedingJoinPoint pjp) throws Throwable { 
    long start = System.currentTimeMillis();
    Object output = pjp.proceed();
    long elapsedTime = System.currentTimeMillis() - start;
    // perform side efects with elapsed time e.g. print, store...
    return output;
}

Or something like

@Around(value = "execution(* package.of.the.class.Test.*(..)) || " +
                "execution(* package.of.the.class.Test2.*(..))")
public Object aroundABCD(ProceedingJoinPoint pjp) throws Throwable { ... }

The second option is to use a CGLIB bean, package private methods and self injection.

You declare a CGLIB bean just using the scope annotation

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Bean public Test test() {
    return new Test();
}

Self injection and package private methods as follows

public class Test {
    @Autowired private Test test;
    // ...
    public String a() {
        test.b(); // call through proxy (it is intercepted)
    }
    String b() { ... } // package private method
    // ...
}

The third solution is to use LWT Load-Time weaving that is using aspectj instead of the spring aop. This allows to intercept method calls even inside the same class. You can use the official spring documentation to implement it, but you will have to use a java agent to run.


Calling method

If you need to know if an specific method made the call to the intercepted function, you can use Thread.currentThread().getStackTrace() for the options 1 or 2. If you use aspectj (option 3), you could intercept the methods with cflow().

Jose Da Silva Gomes
  • 3,814
  • 3
  • 24
  • 34