2

It' well-known that @Transactional or any other AOP around the method won't work if it was called from inside of the same class.

And explanation is clear and always made sense to me: the proxy wraps a real basic Java object. All the other clients of our class actually have a reference to proxy (injected by Spring), that's why AOP logic works. Inside of the same class we have a clean this without AOP, hence AOP does not work if you call AOP-suggesting method directly on this.

But at the same time there are @Configuration classes with @Bean methods for producing singletons which somehow get executed only once even if there is the code which explicitly invokes such method multiple times on this. Here is the practical example:

@Bean
String bean1() {
    System.out.println("Creating bean1 only once");
    return new StringBuilder("bean1").toString();
}

@Bean
StringHolder bean2() {
    return new StringHolder("bean2", bean1());
}

@Bean
StringHolder bean3() {
    return new StringHolder("bean3", bean1());
}

Both bean2() and bean3() explicitly call bean1(), but the "Creating bean1 only once" will be printed only once on context startup. How does it even work? The @Transactional/AOP examples linked above teach us that the framework's magic should not work when we call self methods.

The behavior of @Bean in @Configuration on the other hand is expected according to what Spring Core documentation says:

CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects. Such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans, even when referring to other beans through programmatic calls to @Bean methods

I've decided to check in debugger what is the reference to this inside of a @Bean method of @Configuraiton class and it turned out that this is not just an instance of MyConfigClass, but an instance of MyConfigClass$$SpringCGLIB$$0. So, this is a real evidence that this can somehow point to a CGLIB proxy and not just a basic instance of original class. If it can be achieved, why the same technique is not applied to instances of classes with @Transactional and other AOP?

My first clue was that @Transactional/AOP probably uses different types of proxies, so I've also inspected the reference to MyTransactionalService. Inside of a method of the service this points to instance of basic MyTransactionalService, but outside of the class DI container will inject a reference to MyTransactionalService$$SpringCGLIB$$0.

So, it looks like the same CGLIB approach is used for both cases: MyConfigClass and MyTransactionalService. But why methods of MyConfigClass see proxied object behind this ref, and at the same time methods of MyTransactionalService see bare non-proxied object?

Kirill
  • 6,762
  • 4
  • 51
  • 81
  • 1
    In addition to the information in my answer to the question I marked this one as a duplicate of, I want to mention that AFAIK (never having been a Spring user) you can also use `@Transactional` in native AspectJ mode. In that case, self-invocation should be tracked, because AspectJ does not use proxies. If that really works, however, depends on how the transaction aspects are implemented. I did not check, never having used them. Just give it a try or study the user manual. – kriegaex Dec 23 '22 at 07:34
  • 1
    This question is locked, so I am posting this as a comment. The real reason is inconsistency with the JDK proxying mechanism, as mentioned in the docs: "Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies." Remember, you can't intercept method calls within a custom class using standard JDK proxies since you can't 'extend' the said class. Check out the ["Proxying Mechanism"](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-proxying) section of the docs to learn more. – alchemist Dec 24 '22 at 10:31
  • @alchemist, I am failing to see the relevance of your comment. What you said is firstly untrue, because there is no "inconsistency" mentioned in the documentation. Your interpretation is wrong, IMO. Nothing is inconsistent there. Secondly, the proxying mechanism in the docs only explains how Spring beans are being proxied, not how it works for config classes or bean factory methods. The mechanism there is fundamentally different. With all due respect, please refrain from confusing others with half-baked comments. – kriegaex Dec 24 '22 at 11:37
  • 1
    @kriegaex I don't think it is confusing for Spring users. JDK and CGLIB proxying mechanisms can not be used interchangeably, and Spring favors JDK proxying because it doesn't want to rely on external libraries. Hence, the Spring developers have to use proxies in a way that is compatible with the JDK, even when the classes are being proxied with CGLIB. If you hadn't closed the question, I could provide a more detailed explanation – alchemist Dec 24 '22 at 11:56
  • I know all of this, but it still is irrelevant in this context, because the question asked was not about the difference between JDK and CGLIB proxies. Proxying interface vs. class types technically is not the same, so let us not compare apples and pears. IMO, proxying an interface is preferable to proxying a class, and that is what JDK proxies are made for. So I understand that they are the default. BTW, in Spring Boot CGLIB proxies are the default, even for interface types. That is rather what I would call inconsistent, but not what you said. But we are way OT here, let us stop. – kriegaex Dec 24 '22 at 12:15
  • 1
    @kriegaex I would like to read the complete answer from @alchemist. To me it seems relevant and answers the question: if JDK interface-based proxies are less powerful (have no ability to override `this` with a proxied reference),then it makes perfect sense to not apply the same technique for AOP based on CGLIB proxies (even if they can do so), otherwise the software would behave differently depending on proxy mechanism. If I understood correctly JDK proxies are the "lowest denominator" here, hence Spring developers can't allow CGLIB proxies to do more (to make AOP working with self invocation) – Kirill Dec 24 '22 at 12:25
  • Guys, if you need self-invocation, simply ditch proxies and use native AspectJ. That is what the Spring manual also tells you. Please stop philosophising here, the debate is too long already, and it is off-topic on Stack Overflow. – kriegaex Dec 24 '22 at 14:10

1 Answers1

1

In general that is not a good idea to discuss decisions made by other developers, however I believe I can give some clues...

Let's consider following @Transactional bean:

@Transactional(propagation=PROPAGATION_REQUIRES_NEW)
void method1() {
    // do something
    method3()
    // do something
}

void method2() {
    // do something
    method3()
    // do something
}

@Transactional(propagation=PROPAGATION_REQUIRES_NEW)
void method3() {
    // do something
}

If spring were performing transformations similar to @Configuration classes (i.e. subclassing bean class and overriding @Transactional methods) that might be "desired" behaviour for #method2, but in case of #method1 we could start getting inconsistencies. And in order to overcome possible ambiguities you would need to write something like:

@Transactional(propagation=PROPAGATION_REQUIRES_NEW)
void method1() {
    // do something
    doMethod3()
    // do something
}

void method2() {
    // do something
    method3()
    // do something
}

@Transactional(propagation=PROPAGATION_REQUIRES_NEW)
void method3() {
    doMethod3()
}

void doMethod3() {
    // do something
}

Another example:

@Transactional
void method1() {
    // do something
    try {
       method2()
    } catch (Exception ex) {
       // ignore
    }
    // do something
}

@Transactional
void method2() {
    // do something
}

Now, exceptions raised in #method2 would mark transaction initiated in #method1 as rollback-only regardless of our attempts to catch those exceptions.

Both examples above demonstrate that @Transactional semantics also depends on caller side, and IMO that is much simpler to have a deal with current straightforward implementation, when all @Transactional annotations are basically ignored, rather than solving @Transactional puzzles every day.

FYI, self-injection IMO is not a good idea, the problem is when we do some tricks in code those tricks must be clearly visible and discoverable, it is much better to write something like:

class TransactionService {

   @Transactional
   public <T> T tx(Supplier<T> supplier) {
     return supplier.get();
   }

}
Andrey B. Panfilov
  • 4,324
  • 2
  • 12
  • 18