javaspringspring-bootaopspring-aop

Accessing spring bean proxy reference itself


I have an issue with @Cacheable and @CacheEviction annotations. When I call these methods in the bean where they are declared, the aop part is not getting executed.

The underlying reason for this is that the bean access to its own instance itself, instead of accessing the spring proxy.

I have read this question where it is said that in most cases it should not be necessary a bean accessing the proxy.

Probably those answers work for me. The question is:

Is there any other way to make annotated methods work? Or does it sound like I found a good reason for a bean needing to access the proxy itself?


Solution

  • As is well documented in the Spring user manual, self-invocation cannot work with Spring AOP because Spring AOP uses proxies. So if you want to make self-invocation trigger an aspect, please switch to full AspectJ via LTW (load-time weaving). It works with the original beans and does not use any proxies.


    Update: If you want to avoid using native AspectJ and instead as a (pretty lame and anti-AOP) workaround want to make your component proxy-aware, of course you can use self-injection and reference the cached method using the auto-wired proxy like this:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Component;
    
    @Component
    public class MyComponent {
      @Autowired
      MyComponent myComponent;
    
      public void doSomething() {
        System.out.println(myComponent.doCacheable());
        System.out.println(myComponent.doCacheable());
        System.out.println(myComponent.doCacheable());
      }
    
      @Cacheable("myCache")
      public String doCacheable() {
        return "" + System.nanoTime();
      }
    }
    

    Calling doSomething() on the MyComponent bean should yield output like this:

    247760543178800
    247760543178800
    247760543178800
    

    This proves that caching works like this. If instead you just have three lines of either System.out.println(doCacheable()); or the weird, nonsensical variant from the other (now deleted) answer System.out.println(MyComponent.this.doCacheable());, then you would get three different values on the console, i.e. nothing would be cached.


    Update 2024-04-20: To answer the follow-up question from the comment about cyclic (circular) dependencies when using self-injection in more recent Spring (Boot) versions: Simply read the corresponding error message carefully (added some line breaks for readability without horizontal scrolling):

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    The dependencies of some of the beans in the application context
    form a cycle:
    
    ┌──->──┐
    |  myComponent (field de.scrum_master.spring.q65397019.MyComponent de.scrum_master.spring.q65397019.MyComponent.myComponent)
    └──<-──┘
    
    
    Action:
    
    Relying upon circular references is discouraged and they are
    prohibited by default. Update your application to remove the 
    dependency cycle between beans. As a last resort, it may be possible 
    to break the cycle automatically by setting
    spring.main.allow-circular-references to true.
    

    On more recent JDK versions, reflection usage in the aspect probably also requires an additional --add-opens. I.e., just add this to your JVM command line:

    -Dspring.main.allow-circular-references=true --add-opens java.base/sun.reflect.annotation=ALL-UNNAMED
    

    But like I said, when using native AspectJ, you do not need any of those unsexy hacks.