Spring AOP not working for method call inside another method
Asked Answered
B

16

80

There are two methods defined in ABC.java

public void method1(){
   .........
   method2();
  ...........
}


public void method2(){
  ...............
  ...............  
}

I want to have AOP on call of method2.So, I created one class,AOPLogger.java,having aspect functionality provided in a method checkAccess
In configuration file, I did something like below

<bean id="advice" class="p.AOPLogger" />
<aop:config>
  <aop:pointcut id="abc" expression="execution(*p.ABC.method2(..))" />
  <aop:aspect id="service" ref="advice">
    <aop:before pointcut-ref="abc" method="checkAccess" />          
  </aop:aspect>
</aop:config>

But when my method2 is called, AOP functionality is not getting invoked i.e. checkAccess method is not getting invoked of AOPLogger class.

Any thing i am missing?

Beadsman answered 26/11, 2012 at 12:13 Comment(0)
H
102

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)

Harber answered 26/11, 2012 at 12:17 Comment(6)
Sorry,didn't get..could you please explain in simple terms as i am new to it or provide any good linkBeadsman
I think the Spring doc link contains quite a lot of info. But I've expanded a little in the aboveHarber
The aspects defined around the methods in Spring services did not run and thought it was due to a bad configuration. In the end I found this question and answer that saved me several hours .. thanks!Bighorn
@BrianAgnew - Do you have any idea of other "Non-Spring-Oriented AOP" framework?Covin
@Covin use AspectJ directly, either at build time with the AspectJ compiler or at run-time with the AspectJ Load-Time Weaver.Atrabilious
you said "adding functionality, such as your AOP". What additional functionality is added besides AOP? Can you plz provide some examplesCrossbeam
T
42

Update 2022:

Now I personally prefer using TransactionHandler class described here - much cleaner and flexible way.

Original answer:

It can be done by self injection usage. You can call inner method through injected instance:

@Component
public class Foo {
    @Resource
    private Foo foo;
    
    public void method1(){
        ..
        foo.method2();
        ..
    }
    public void method2(){
        ..
    }
}

Since Spring 4.3 you also can do it using @Autowired.

As of 4.3, @Autowired also considers self references for injection, i.e. references back to the bean that is currently injected.

Tombaugh answered 30/8, 2016 at 14:48 Comment(3)
Yes but what about the poor Foo#foo#foo ?Deboer
It's just a workaround. I haven't said it is perfect, but it worksTombaugh
I get APPLICATION FAILED TO START The dependencies of some of the beans in the application context form a cycle: in Spring 2.3.4Weightlessness
C
12

Spring AOP framework is "proxy" based, the documentation at Understanding AOP Proxies explains it very well.

When Spring constructs a bean that is configured with an aspect (like "ABC" in your example), it actually creates a "proxy" object that acts like the real bean. The proxy simply delegates the calls to the "real" object but by creating this indirection, the proxy gets a chance to implement the "advice". For example, your advice can log a message for each method call. In this scheme, if the method in the real object ("method1") calls other methods in the same object (say, method2), those calls happen without proxy in the picture so there is no chance for it to implement any advice.

In your example, when method1() is called, the proxy will get a chance to do what ever it is supposed to do but if method1() calls method2(), there is no aspect in the picture. However, if method2 is called from some other bean, the proxy will be able to carry out the advice.

Casket answered 26/11, 2012 at 19:54 Comment(0)
F
6

I had the same kind of problem and I overcame by implementing Spring's ApplicationContextAware,BeanNameAware and implementing corresponding methods as below.

class ABC implements ApplicationContextAware,BeanNameAware{

      @Override
      public void setApplicationContext(ApplicationContext ac) throws BeansException {
          applicationContext=ac;
      }

      @Override
      public void setBeanName(String beanName) {
          this.beanName=beanName;
      }
      private ApplicationContext applicationContext;
      private String beanName;
}

then I replaced this. with ((ABC) applicationContext.getBean(beanName)). while calling the methods of the same class. This ensures that calls to methods of the same class happen through the proxy only.

So method1() changes to

 public void method1(){
    .........
    ((ABC) applicationContext.getBean(beanName)).method2();
    ...........
  }

Hope this helps.

Farleigh answered 19/9, 2013 at 14:38 Comment(1)
While this method certainly works, disadvantage is that your class is now tightly coupled to spring framework. If you were using pure xml configuration and not annotations, your class could be independent on Spring. Usually best practice is to refactor logic to be divided to two separate beans. However, if coupling to spring is not your concern, this solution might be good enough.Institute
L
3

Using @Autowired it works. Instead of calling the inner method as this.method(), you can do:

@Autowired
Foo foo;

and then calling:

foo.method2();
Latinity answered 11/10, 2017 at 20:4 Comment(2)
but this should result in runtime error as you end up in circular dependencyThievish
You'll get APPLICATION FAILED TO START The dependencies of some of the beans in the application context form a cycle:Weightlessness
A
1

Lazy! I had the same problem. I use Autowired with Lazy and it is works.

@Component
public class Foo {

    @Lazy
    @Autowired
    private Foo foo;
    
    public void method1(){
        ..
        foo.method2();
        ..
    }
    public void method2(){
        ..
    }
}
Abyss answered 27/9, 2022 at 13:47 Comment(0)
I
0

It is not possible what you want to achieve. An explanation is in the Spring Reference Documentation.

Infundibuliform answered 26/11, 2012 at 12:17 Comment(0)
H
0

As indicated in the Spring docs, chapter 5.6.1 Understanding AOP proxies, there is another way you can do:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

Although the author doesn't recommand this way. Because:

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created.

Hypocrisy answered 10/9, 2018 at 7:5 Comment(0)
E
0

Annotate calls with @EnableAspectJAutoProxy(exposeProxy = true) and call the instance methods with ((Class) AopContext.currentProxy()).method();

This is strictly not recommended as it increases to coupling

Electrify answered 27/11, 2018 at 10:11 Comment(0)
G
0

I am surprised no one mentioned this but i think we can use ControlFlowPointcut provided by Spring.

ControlFlowPointcut looks at stacktrace and matches the pointcut only if it finds a particular method in the stacktrace. essentially pointcut is matched only when a method is called in a particular context.

In this case, we can create a pointcut like

ControlFlowPointcut cf = new ControlFlowPointcut(MyClass.class, "method1");

now, using ProxyFactory create a proxy on MyClass instance and call method1().

In above case, only method2() will be advised since it is called from method1().

Gadabout answered 1/3, 2019 at 19:54 Comment(0)
I
0

Another way around is to mode the method2() to some other Class File ,and that class is annotated with @Component. Then inject that using @Autowired when you need that.This way AOP can intercept that.

Example:

You were doing this...


Class demo{
   method1(){
    -------
    -------
    method2();
    -------
    -------
   }

   method2(){
    ------
    -----
   }
}

Now if possible do this :

@Component
class NewClass{
    method2(){
    ------
    -----
   }
}


Class demo{

 @AutoWired
 NewClass newClass;

   method1(){
    -------
    -------
    newClass.method2();
    -------
    -------
   }

}
Isabea answered 4/8, 2019 at 4:16 Comment(0)
Q
0

You can refer to this question: https://mcmap.net/q/152377/-spring-aop-not-working-when-the-method-is-called-internally-within-a-bean

It's a workaround but will do the trick, the key here is to use the same bean (proxy) to invoke 'method2' instead of this.

Querida answered 7/12, 2019 at 14:23 Comment(0)
H
0

After a lot of research, I found the following which works like a charm. But there is always a better way with ASPECTJ WEAVING. In essence of time use a self reference.

@Autowired
private ApplicationContext applicationContext;
private <<BEAN>> self;

Please note the <> is of the same class which needs to be logged with AOP or to be used for something else.

@PostConstruct
private void init() {
    self = applicationContext.getBean(MyBean.class);
}

With this change all you have to do is move the calls for

this.methodName(args) -> self.methodName(args)

Hope this helps all who want to use a solution for just performance logging in the application.

Holily answered 8/5, 2020 at 9:52 Comment(0)
F
0

You could do this :

@Autowired // to make this bean refers to proxy class 

ABC self;

public void method1(){

   .........

   self.method2();

  ...........

}


public void method2(){

  ...............

  ...............  

}
Florenceflorencia answered 2/2, 2021 at 10:55 Comment(0)
M
0

AOP prevents you to wrap a function if it is in the caller is in the same class of the callee.

But you can do the below work around to achieve the above by using self-injection

public class Test {


  @Autowire 
  private Test test;


  public void method1(){
     .........
     test.method2();
    ...........
  }


  public void method2(){
    ...............
    ...............  
  }
}

notice the self-injection when I did

  @Autowire 
  private Test test;

and I did call the method 2 by test.method2(); instead of this.method2()

Moquette answered 21/7, 2022 at 13:4 Comment(0)
A
-1

You can do self-injection this way so that this class can be used outside Spring application.

@Component
public class ABC {

    @Resource
    private ABC self = this;

    public void method1() {
        self.method2();
    }

    public void method2() {

    }

}
Adust answered 27/11, 2018 at 4:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.