5

As we all know, the self-invokation of bean's method is not working in Spring without AspectJ.

See this question for example.

I think this is because the Spring-created proxy calls the target object's methods using delagate pattern. Like this:

class MyClass {

    @Autowired
    private MyClass self; // actually a MyProxy instance

    @Transactional // or any other proxy magic
    public void myMethod() {}

    public void myOtherMethod() {
        this.myMethod(); // or self.myMethod() to avoid self-invokation problem
    }
}

class MyProxy extends MyClass { // or implements MyInterface if proxyMode is not TARGET_CLASS and MyClass also implements MyInterface

    private final MyClass delegate;

    @Override
    public void myMethod() {
        // some proxy magic: caching, transaction management etc
        delegate.myMethod();
        // some proxy magic: caching, transaction management etc
    }

    @Override
    public void myOtherMethod() {
        delegate.myOtherMethod();
    }
}

Am I right?

With this code:

public void myOtherMethod() {
    this.myMethod();
}

this.myMethod() will bypass the proxy (so all @Transactional or @Cacheable magic) because it is just internal delegate's call... So we should inject a MyClass bean (which is actually is MyProxy instance) inside MyClass and call self.myMethod() instead. It is understandable.

But why the proxy is implemented this way? Why it is not just extends the target class, overriding all public methods and calling super instead of delegate? Like this:

class MyProxy extends MyClass {
    // private final MyClass delegate; // no delegate
    @Override
    public void myMethod() {
        // some proxy magic: caching, transaction management etc
        super.myMethod();
        // some proxy magic: caching, transaction management etc
    }
    @Override
    public void myOtherMethod() {
        super.myOtherMethod();
    }
}

It should solve the self-invokation problem, where this.myMethod() bypasses the proxy, because in this case this.myMethod(), invoked from MyClass.myOtherMethod() (we remember that MyClass bean actually is MyProxy instance), will invoke overriden child's method (MyProxy.myMethod()).

So, my main question is why it is not implemented this way?

Ruslan Stelmachenko
  • 4,987
  • 2
  • 36
  • 51
  • 1
    I am not askinkg why *Java dynamic proxies* are implemented the way they are. I am asking why *Spring AOP proxies* implemented the way they are. As far as I know, they use CGLIB or Javassist in proxy-mode TARGET_CLASS, and not Java dynamic proxies at all. And yes, my question is really that. Many years, every time I use self-injection I cry (this is error-prone ugly pattern and LTW have own problems). So I want to really know why Spring devs not implement this using `super`(is there are reason or just "it happened"). And I'm sure many DEVs many years asked himself same question again and again – Ruslan Stelmachenko Jan 23 '18 at 14:03
  • No. By default Spring uses Java dynamic proxies when proxying interfaces. Only if you you need to proxy classes not implementing an interface, you need CGLIB. Optionally, you can switch it to also use CGLIB for interfaces. Anyway, both are pretty much similar in that they are dynamic proxies and do not support internal calls via `this`. And BTW, how can I know which mode you use? You did not show any configuration. I can only guess from your code, maybe. BTW2, the close vote you received is not mine, I was just mentioning it. You are welcome for my hint about AspectJ (no proxies there). – kriegaex Jan 23 '18 at 15:18
  • FYI, proxy-mode TARGET_CLASS (CGLIB) is default mode in Spring Boot 2.0. Also the code in question intentionally doesn't use any interface so it will use CGLIB in any Spring version anyway. PS. Chill, I don't worry about close-votes. I understand that question is not entirely well-suited for SO. I have a little hope that Juergen Hoeller (maybe the only person in the world who can give a meaningful asnwer) actually asnwer it. :-) It was just a try, but maybe.. – Ruslan Stelmachenko Jan 24 '18 at 01:44
  • I **did** give you a meaningful answer: Use AspectJ if you do not like the way Spring AOP works. No dynamic proxies (neither CGLIB nor Java), no worries. Philosophical discussions or guesswork about why many years ago someone might have made a design decision this or that way are really out of place on SO. My shot at it would be: Java dynamic proxies existed in the JRE, CGLIB also was around, so Springsource just used them. P.S.: You did not tag the question _spring-boot_, so how can I know about the relevance of its defaults? I do not use Spring, I just happen to know something about it. – kriegaex Jan 24 '18 at 01:55
  • Yes, you did give an answer, but on a totally different question (which I didn't ask at all). :-) The question is why CGLIB proxies implemented using [Delegation pattern](https://en.wikipedia.org/wiki/Delegation_pattern) and not [Inheritance](https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)) (which should solve `this` problem). And it is not philosofical discussion! It is real precise question addressed to spring-experts. And discussion in comments is also something not good for SO, so let's close it. If you really want to discuss - let's go to chat. Thanks. – Ruslan Stelmachenko Jan 24 '18 at 15:41

2 Answers2

3

Your assumption that Spring AOP uses delegation for its proxies is correct. This is also documented.

Using CGLIB, you can theoretically use proxy.invokeSuper() in order to achieve the effect you want, i.e. that self-invocation is registered by the aspect implemented by the proxy's method interceptor (I am using Spring's embedded version of CGLIB here, thus the package names):

package spring.aop;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

class SampleClass {
  public void x() {
    System.out.println("x");
    y();
  }

  public void y() {
    System.out.println("y");
  }

  public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallback(new MethodInterceptor() {
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable {
        if(method.getDeclaringClass() == Object.class)
          return proxy.invokeSuper(obj, args);
        System.out.println("Before proxy.invokeSuper " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After proxy.invokeSuper " + method.getName());
        return result;
      }
    });
    SampleClass proxy = (SampleClass) enhancer.create();
    proxy.x();
  }
}

Console log:

Before proxy.invokeSuper x
x
Before proxy.invokeSuper y
y
After proxy.invokeSuper y
After proxy.invokeSuper x

This is exactly what you want. The problem starts, however, when you have several aspects: transactions, logging, whatever else. How do you make sure that they all work together?

  • Option 1: Each aspect gets its own proxy. This obviously will not work unless you nest the proxies into each other according to aspect precedence. But nesting them into each other means inheritance, i.e. one proxy would have to inherit from the other outside-in. Try proxying a CGLIB proxy, it does not work, you get exceptions. Furthermore, CGLIB proxies are quite expensive and use perm-gen memory, see descriptions in this CGLIB primer.

  • Option 2: Use composition instead of inheritance. Composition is more flexible. Having one proxy to which you can register aspects as needed solves the inheritance problem, but also means delegation: The proxy registers the aspects and calls their methods during runtime in the right order before/after the actual real object's code is executed (or not, if an @Around advice never calls proceed()). See this example from the Spring manual about manually registering aspects to a proxy:

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

As to why the Spring developers chose this approach and whether it might have been possible to use the one-proxy approach but still make sure that self-invocation works like in my little CGLIB sample "logging aspect" above, I can only speculate. You can maybe ask them on the developers mailing list or look into the source code. Maybe the reason was that CGLIB proxies should behave similarly to the default Java dynamic proxies so as to make switching between the two for interface types seamless. Maybe the reason is another one.

I did not mean to be rude in my comments, only straightforward, because your question is really not suited to StackOverflow because it is not a technical problem to which someone can find a solution. It is a historical design question and rather philosophic in nature because with AspectJ a solution to your technical problem (self-invocation) beneath the actual question already exists. But maybe you still want to dive into the Spring source code, change the Spring AOP implementation from delegation to proxy.invokeSuper() and file a pull request. I am not sure such a breaking change would be accepted, though.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks! This is really helpful answer and I'll accept it, because it really asnwers the question! Especially this part "Try proxying a CGLIB proxy, it does not work, you get exceptions. Furthermore, CGLIB proxies are quite expensive and use perm-gen memory." Of course the proxy should be nestable or else it will not be eligible for generic use-cases. So this fact alone is enough to understand why 'call super' approach will not work. So, thanks! – Ruslan Stelmachenko Jan 26 '18 at 16:51
  • For about suitability of the question to StackOverflow. It is of course discussable, and maybe you are right, but SO is full of questions like this: https://stackoverflow.com/questions/20129762/why-does-streamt-not-implement-iterablet for example. And number of upvotes of question and asnwers suggests that people need it. On the one hand this question can be considered philosophical, but on the other - it is possible to give a very concrete answer, which can help to understand why a considered approach can be bad (so why it was implemented in other way). – Ruslan Stelmachenko Jan 26 '18 at 18:10
  • Just because others do something it does not necessarily mean it is good. Please note that my answer is in some parts speculative and I do not have a good feeling about making educated guesses instead of knowing for sure. But I do agree that I like the feeling to have helped you and answered your question, so thanks for the positive feedback. I also learned something about CGLIB - which I had never used directly before - when creating the sample code, so for me it was also worth the while. But that is also a rather selfish motive and as such does not justify questions like these on SO. ;-) – kriegaex Jan 27 '18 at 03:46
0

In addition, you will not able to use Inheritance + super in the following cases:

  • What about if the RealSubject is final, so the proxy will can NOT extends it
  • What about if the Proxy needs to extend something other than the RealSubject
  • What about if you need to hide some functionality (methods) inside the RealSubject
  • Prefer Composition over Inheritance (recommended by many developers)
Ahmed Nabil
  • 17,392
  • 11
  • 61
  • 88