Spring Aop Not Working for Method Call Inside Another Method

Spring AOP not working for method call inside another method

The aspect is applied to a proxy surrounding the bean. Note that everytime you get a reference to a bean, it's not actually the class referenced in your config, but a synthetic class implementing the relevant interfaces, delegating to the actual class and adding functionality, such as your AOP.

In your above example you're calling directly on the class, whereas if that class instance is injected into another as a Spring bean, it's injected as its proxy, and hence method calls will be invoked on the proxy (and the aspects will be triggered)

If you want to achieve the above, you could split method1/method2 into separate beans, or use a non-spring-orientated AOP framework.

The Spring doc (section "Understanding AOP Proxies") details this, and a couple of workarounds (including my first suggestion above)

Spring AOP not working, when the method is called internally within a bean

Thank you jst for clearing the things up. Just for the information purposes for the future developer in SO, I'm posting the full answer to this question



Lets assume that there is a bean from SimplePojo

public class SimplePojo implements Pojo {
public void foo() {
this.bar();
}
public void bar() {
...
}
}

When we call the method foo(), it reinvokes the method bar() inside it. Even thought the method foo() is invoked from the AOP Proxy, the internal invocation of the bar() is not covered by the AOP Proxy.

Proxy calls

So eventually this makes, if there are any advices attached to the method bar() to not get invoked

Solution

Use AopContext.currentProxy() to call the method. Unfortunately this couples the logic with AOP.

public void foo() {
((Pojo) AopContext.currentProxy()).bar();
}

Reference:

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies

Do some action after a method call inside another method

As Gervasio Amy suggested, you need to use AspectJ, not Spring AOP. If you are in a Spring environment, you can use AspectJ within Spring instead of Spring AOP, this is no problem. If you are not using Spring yet, AOP is not a reason to start using it, AspectJ works in simple Java SE (or EE) versions without Spring.

What you need to to is:

  • Compile your Aspect code with the AspectJ compiler ajc. (You can also compile your whole application with it because it is also a replacement for the Java compiler javac.)
  • Create a load-time weaving configuration aop.xml so as to enable your application to weave aspect code into 3rd party libraries on the fly during class-loading. I leave it up to you to figure out how to do that, just check the LTW documentation.
  • Start your JVM or application server with the AspectJ weaving agent on the command line via -javaagent:/path/to/aspectjweaver.jar switch.

Now what would the aspect you want look like? Let us try a few variants and refine the pointcut so as to make it match. But first let us set the stage for our experiments with a few sample 3rd party classes (Foo and Bar) and a little driver application (Application):

Sample application & 3rd party code:

package my.thirdparty.application;

public class Foo {
void blah() {
zot();
}

void foo() {}

void zot() {
foo();
}
}
package my.thirdparty.application;

public class Bar {
Foo foo = new Foo();

public void doSomething() {
someMethod();
bar();
anotherMethod();
}

private void someMethod() {
foo.blah();
foo.foo();
foo.zot();
}

private void bar() {
foo.blah();
// This is the only call we want to intercept, 'foo' called by 'bar'
foo.foo();
foo.zot();
anotherMethod();
}

private void anotherMethod() {
foo.blah();
foo.foo();
foo.zot();
}
}
package de.scrum_master.app;

import my.thirdparty.application.Bar;

public class Application {
public static void main(String[] args) {
new Bar().doSomething();
}
}

As you can see, Application.main creates a Bar object and calls a public method Bar.doSomething. This method triggers a series of other method calls, some of which end up in Foo.foo being called indirectly, but only one single direct call is being made from Bar.bar to Foo.foo (which is what we are interested in according to your question).

Aspect, part #1: intercept all calls to Foo.foo

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
pointcut allCalls() :
call(* Foo.foo(..));

Object around(Foo fooObject) : allCalls() && target(fooObject) {
System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
//new Exception("printing stack trace").printStackTrace(System.out);
//System.out.println();
return proceed(fooObject);
}
}

Console log:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.someMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())

This is a nice start, because now we can already intercept all calls to Foo.foo. But how about limiting the interceptions to those calls which are being made from within a control flow (cflow) of Bar.bar?

Aspect, part #2: intercept calls to Foo.foo made (in-)directly by Bar.bar

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
pointcut indirectCalls() :
call(* Foo.foo(..)) && cflow(execution(* Bar.bar(..)));

Object around(Foo fooObject) : indirectCalls() && target(fooObject) {
System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
//new Exception("printing stack trace").printStackTrace(System.out);
//System.out.println();
return proceed(fooObject);
}
}

Console log:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())

Now this looks much better than before, we narrowed down our previous result of intercepted 12 calls down to 6. But how does it happen that we have callers like Foo.zot and Bar.anotherMethod in the result list, even though we said we wanted to limit the control flow to Bar.bar? The answer is simple: Those two methods were also directly or indirectly called by Bar.bar and are thus within the control flow. We see this more clearly if we inspect the call stacks (just uncomment the two log statements in the code):

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
at my.thirdparty.application.Foo.zot(Foo.java:11)
at my.thirdparty.application.Foo.blah(Foo.java:5)
at my.thirdparty.application.Bar.bar(Bar.java:19)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Bar.foo_aroundBody3$advice(Bar.java:22)
at my.thirdparty.application.Bar.bar(Bar.java:21)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
at my.thirdparty.application.Foo.zot(Foo.java:11)
at my.thirdparty.application.Bar.bar(Bar.java:22)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
at my.thirdparty.application.Foo.zot(Foo.java:11)
at my.thirdparty.application.Foo.blah(Foo.java:5)
at my.thirdparty.application.Bar.anotherMethod(Bar.java:27)
at my.thirdparty.application.Bar.bar(Bar.java:23)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Bar.foo_aroundBody5$advice(Bar.java:22)
at my.thirdparty.application.Bar.anotherMethod(Bar.java:28)
at my.thirdparty.application.Bar.bar(Bar.java:23)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Foo.zot())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Foo.foo_aroundBody1$advice(Foo.java:22)
at my.thirdparty.application.Foo.zot(Foo.java:11)
at my.thirdparty.application.Bar.anotherMethod(Bar.java:29)
at my.thirdparty.application.Bar.bar(Bar.java:23)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

If you inspect the 6 callstacks, you find Bar.bar in each of them. So the cflow pointcut has done just what we told it to.

Can we get even better? How about telling the aspect not just limit the callee (target) object to Foo but also the to also the caller (this) object to Bar?

Aspect, part #3: intercept calls to Foo.foo made (in-)directly by Bar.bar, but definitely from a Bar object

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
pointcut callsFromBar(Bar barObject) :
call(* Foo.foo(..)) && cflow(execution(* Bar.bar(..))) && this(barObject);

Object around(Foo fooObject, Bar barObject) : callsFromBar(barObject) && target(fooObject) {
System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
new Exception("printing stack trace").printStackTrace(System.out);
System.out.println();
return proceed(fooObject, barObject);
}
}

Console log:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Bar.foo_aroundBody3$advice(Bar.java:22)
at my.thirdparty.application.Bar.bar(Bar.java:21)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.anotherMethod())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Bar.foo_aroundBody5$advice(Bar.java:22)
at my.thirdparty.application.Bar.anotherMethod(Bar.java:28)
at my.thirdparty.application.Bar.bar(Bar.java:23)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

We are getting better and better: down to 2 interceptions from 6. The one from Bar.anotherMethod is still unwanted because it was only indirectly triggered by Bar.bar and our ambition is to only intercept direct calls. Okay, then let us get even more precise:

Aspect, part #4: intercept calls to Foo.foo made directly by Bar.bar, no indirection permitted

package de.scrum_master.aspect;

import my.thirdparty.application.Foo;
import my.thirdparty.application.Bar;

public aspect MethodInterceptor {
pointcut directCalls(Bar barObject) :
call(* Foo.foo(..)) && cflow(execution(* Bar.bar(..))) && this(barObject) &&
if("bar".equals(thisEnclosingJoinPointStaticPart.getSignature().getName()));

Object around(Foo fooObject, Bar barObject) : directCalls(barObject) && target(fooObject) {
System.out.println(thisJoinPointStaticPart + " -> caller = " + thisEnclosingJoinPointStaticPart);
new Exception("printing stack trace").printStackTrace(System.out);
System.out.println();
return proceed(fooObject, barObject);
}
}

Console log:

call(void my.thirdparty.application.Foo.foo()) -> caller = execution(void my.thirdparty.application.Bar.bar())
java.lang.Exception: printing stack trace
at my.thirdparty.application.Bar.foo_aroundBody3$advice(Bar.java:22)
at my.thirdparty.application.Bar.bar(Bar.java:21)
at my.thirdparty.application.Bar.doSomething(Bar.java:8)
at de.scrum_master.app.Application.main(Application.java:7)

Et voilà! This is what we wanted in the first place. Let us recapitulate what we have just done in order to narrow down the pointcut:

  • call(* Foo.foo(..)) - only calls to Foo.foo
  • cflow(execution(* Bar.bar(..))) - only with the execution of Bar.bar in the control flow
  • this(barObject) - the caller must be a Bar object
  • target(fooObject) - the callee must be a Foo object
  • if("bar".equals(thisEnclosingJoinPointStaticPart.getSignature().getName())) - a dynamic runtime condition checks if the direct caller's method name is really bar

I hope this solves your problem and was not too verbose. I wanted to do it tutorial-style so as to enable you to understand how to solve advanced AOP problems like this one. Enjoy!

Check in Spring AOP if method is called by another annotated method

Like I said, if you would switch to native AspectJ you could use percflow() aspect instantiation and/or cflow() pointcuts, but I will not explain that here in detail because you are looking for a Spring AOP solution. Like R.G said, it is actually quite simple: Use a thread-local counter for the aspect nesting level and only do something (e.g. deleting something in the after advice) if the counter is zero.

@Aspect
@Component
public class DeletedAwareAspect {
private ThreadLocal<Integer> nestingLevel = ThreadLocal.withInitial(() -> 0);

@Before("@within(com.example.DeleteAware)")
public void aroundExecution(JoinPoint pjp) {
int level = nestingLevel.get() + 1;
nestingLevel.set(level);
System.out.println("BEFORE " + pjp + " -> " + level);
}

@After("@within(com.example.DeleteAware)")
public void cleanUp(JoinPoint pjp) {
int level = nestingLevel.get() - 1;
nestingLevel.set(level);
System.out.println("AFTER " + pjp + " -> " + level);
if (level == 0)
System.out.println("Deleting something");
}
}

Spring AOP within doesn't work with method

You are currently doing something similar to what I explained in this answer, i.e. matching (meta) annotations on classes.

Now you are wondering why it does not match methods. I explained that here. Basically, @within() matches anything in annotated classes while @annotation() matches annotated methods. The problem is, @annotation() needs an exact type name.

But there is another way to express an annotated method directly inside an execution() signature. Here you also have the option to specify meta annotations in a similar way as you are using it for annotated classes. Let us compare the two:

package de.scrum_master.aspect;

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

@Aspect
public class MetaAnnotationInterceptor {
@Before(
"execution(* *(..)) && (" +
"within(@de.scrum_master.app.MetaAnnotation *) || " +
"within(@(@de.scrum_master.app.MetaAnnotation *) *) || " +
"within(@(@(@de.scrum_master.app.MetaAnnotation *) *) *)" +
")"
)
public void annotatedClasses(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}

@Before(
"execution(@de.scrum_master.app.MetaAnnotation * *(..)) || " +
"execution(@(@de.scrum_master.app.MetaAnnotation *) * *(..)) || " +
"execution(@(@(@de.scrum_master.app.MetaAnnotation *) *) * *(..)) "
)
public void annotatedMethods(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}

The latter is what you are looking for. Just replace de.scrum_master.app.MetaAnnotation by org.springframework.transaction.annotation.Transactional, and it should work for your use case. Make sure not to mess up the number and nesting order of (), @ and *, otherwise you quickly end up with pointcut syntax errors.

If you prefer to have one advice method instead or two, you can either create a big messy string containing both pointcuts or you define two separate @Pointcuts and combine them in the advice, chaining them with ||.



Related Topics



Leave a reply



Submit