20

Please explain, why self invocation on proxy performed on target but not proxy? If that made on purpose, then why? If proxies created by subclassing, it's possible to have some code executed before each method call, even on self invocation. I tried, and I have proxy on self invocation

public class DummyPrinter {
    public void print1() {
        System.out.println("print1");
    }

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

    public void printBoth() {
        print1();
        print2();
    }
}
public class PrinterProxy extends DummyPrinter {
    @Override
    public void print1() {
        System.out.println("Before print1");
        super.print1();
    }

    @Override
    public void print2() {
        System.out.println("Before print2");
        super.print2();
    }

    @Override
    public void printBoth() {
        System.out.println("Before print both");
        super.printBoth();
    }
}
public class Main {
    public static void main(String[] args) {
        DummyPrinter p = new PrinterProxy();
        p.printBoth();
    }
}

Output:

Before print both
Before print1
print1
Before print2
print2

Here each method called on proxy. Why in documentation mentioned that AspectJ should be used in case of self invocation?

kriegaex
  • 63,017
  • 15
  • 111
  • 202
krund
  • 740
  • 1
  • 7
  • 16

1 Answers1

25

Please read this chapter in the Spring manual, then you will understand. Even the term "self-invocation" is used there. If you still do not understand, feel free to ask follow-up questions, as long as they are in context.


Update: Okay, now after we have established that you really read that chapter and after re-reading your question and analysing your code, I see that the question is actually quite profound (I even upvoted it) and worth answering in more detail.

Your (false) assumption about how it works

Your misunderstanding is about how dynamic proxies work because they do not work as in your sample code. Let me add the object ID (hash code) to the log output for illustration to your own code:

package de.scrum_master.app;

public class DummyPrinter {
  public void print1() {
    System.out.println(this + " print1");
  }

  public void print2() {
    System.out.println(this + " print2");
  }

  public void printBoth() {
    print1();
    print2();
  }
}
package de.scrum_master.app;

public class PseudoPrinterProxy extends DummyPrinter {
  @Override
  public void print1() {
    System.out.println(this + " Before print1");
    super.print1();
  }

  @Override
  public void print2() {
    System.out.println(this + " Before print2");
    super.print2();
  }

  @Override
  public void printBoth() {
    System.out.println(this + " Before print both");
    super.printBoth();
  }

  public static void main(String[] args) {
    new PseudoPrinterProxy().printBoth();
  }
}

Console log:

de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print both
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print1
de.scrum_master.app.PseudoPrinterProxy@59f95c5d Before print2
de.scrum_master.app.PseudoPrinterProxy@59f95c5d print2

See? There is always the same object ID, which is no surprise. Self-invocation for your "proxy" (which is not really a proxy but a statically compiled subclass) works due to polymorphism. This is taken care of by the Java compiler.

How it really works

Now please remember we are talking about dynamic proxies here, i.e. subclasses and objects created during runtime:

  • JDK proxies work for classes implementing interfaces, which means that classes implementing those interfaces are being created during runtime. In this case there is no superclass anyway, which also explains why it only works for public methods: interfaces only have public methods.
  • CGLIB proxies also work for classes not implementing any interfaces and thus also work for protected and package-scoped methods (not private ones though because you cannot override those, thus the term private).
  • The crucial point, though, is that in both of the above cases the original object already (and still) exists when the proxies are created, thus there is no such thing as polymorphism. The situation is that we have a dynamically created proxy object delegating to the original object, i.e. we have two objects: a proxy and a delegate.

I want to illustrate it like this:

package de.scrum_master.app;

public class DelegatingPrinterProxy extends DummyPrinter {
  DummyPrinter delegate;

  public DelegatingPrinterProxy(DummyPrinter delegate) {
    this.delegate = delegate;
  }

  @Override
  public void print1() {
    System.out.println(this + " Before print1");
    delegate.print1();
  }

  @Override
  public void print2() {
    System.out.println(this + " Before print2");
    delegate.print2();
  }

  @Override
  public void printBoth() {
    System.out.println(this + " Before print both");
    delegate.printBoth();
  }

  public static void main(String[] args) {
    new DelegatingPrinterProxy(new DummyPrinter()).printBoth();
  }
}

See the difference? Consequently the console log changes to:

de.scrum_master.app.DelegatingPrinterProxy@59f95c5d Before print both
de.scrum_master.app.DummyPrinter@5c8da962 print1
de.scrum_master.app.DummyPrinter@5c8da962 print2

This is the behaviour you see with Spring AOP or other parts of Spring using dynamic proxies or even non-Spring applications using JDK or CGLIB proxies in general.

Is this a feature or a limitation? I as an AspectJ (not Spring AOP) user think it is a limitation. Maybe someone else might think it is a feature because due to the way proxy usage is implemented in Spring you can in principle (un-)register aspect advices or interceptors dynamically during runtime, i.e. you have one proxy per original object (delegate), but for each proxy there is a dynamic list of interceptors called before and/or after calling the delegate's original method. This can be a nice thing in very dynamic environments. I have no idea how often you might want to use that. But in AspectJ you also have the if() pointcut designator with which you can determine during runtime whether to apply certain advices (AOP language for interceptors) or not.

Solutions

What you can do in order to solve the problem is:

  • Switch to native AspectJ, using load-time weaving as described in the Spring manual. Alternatively, you can also use compile-time weaving, e.g. via AspectJ Maven plugin.

  • If you want to stick with Spring AOP, you need to make your bean proxy-aware, i.e. indirectly also AOP-aware, which is less than ideal from a design point of view. I do not recommend it, but it is easy enough to implement: Simply self-inject a reference to the component, e.g. @Autowired MyComponent INSTANCE and then always call methods using that bean instance: INSTANCE.internalMethod(). This way, all calls will go through proxies and Spring AOP aspects get triggered.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • 1
    Hi. Of course I have read it. I don't understand, why this limitation on self invocation introduced? Is there any benefit from it? If I proxy a methods, intuitively I expect that this code will be executed. Eaven if it's a feature, why there said that AspectJ should be used for self-invocation, if it's possible to get same result by subclasing? – krund Jun 16 '19 at 09:17
  • An example with a delegate looks like a decorator, but the intent is different. Both proxy and decorator are structural patterns. And both of them are wrapping another object. – Miroslav Trninic Sep 30 '20 at 20:05
  • Both proxy and decorator have a delegate. The original proxy pattern is exactly what we see with JDK dynamic proxies, i.e. both delegate and proxy implement the same interface. The decorator pattern would also do that but have a base decorator and any number of concrete decorators extending the base decorator, i.e. the structure is different from a proxy. My sample code is rather what a CGLIB proxy does, i.e. extend a concrete class. Both proxy and decorator can add functionality. This does not make Spring proxies decorators, but this is quite academic and does not help much. – kriegaex Oct 01 '20 at 01:53
  • As a late reply to @krund: My answer already explains it: Dynamic proxies extend an original object during runtime(!) not during compile time like a subclass you write in your IDE and then compile it together with the base class. You would always only create a subclass instance during runtime, but the Spring way is to create the original object and then an additional proxy. Hence, self-invocation (method calls via `this`) **cannot** work the way you expect, it is impossible with a proxy pattern. But as I said, AspectJ does not use proxies, so you can just use it if you need self-invocation. – kriegaex Oct 01 '20 at 01:58
  • I still did not figure out why aop not work for self invokation. – Dolphin Sep 24 '22 at 18:51
  • Then I cannot help you any better, sorry. I explained in detail with an example, linked to documentation and commented on my answer. There is only so much I can do. Maybe you just relax, read again and give it some time to sink it. It is actually not so complicated. Maybe stepping through my example in a debugger or making some sketches on paper helps you to wrap your head around it. Good luck! P.S.: It is only Spring AOP due to its proxy delegate pattern, which does not support self-invocation. Native AspectJ does not suffer from that problem, because it does not use proxies. – kriegaex Sep 24 '22 at 19:36
  • CGLIB in itself is capable of intercepting self-invocations. As an example, the `Enhancer` built for `@Configuration` classes intercepts calls to `@Bean` methods on the same object The decorator/delegate pattern is explicitly specified elsewhere (see how `CglibAopProxy::invoke` delegates to `this.advised.getTargetSource().getTarget()`), my guess is that they did it this way to better the behaviour of JDK proxies. – MikaelF Feb 09 '23 at 02:48
  • 1
    I am aware of the fact that CGLIB can also intercept self-invocations. I cannot say anything intelligent about why the Spring development team decided to use a delegator pattern for bean/component proxies, however. My answer above is merely explaining the fact that they do, and my code mimicks the behaviour for illustration. I cannot change Spring AOP for you, and the alternative to use native AspectJ as a full-featured AOP solution is documented in the Spring manual. So there is no need for them to add breaking changes to Spring AOP either. – kriegaex Feb 09 '23 at 07:20