Add the async module
Every @Async behaviour that surprises people, asserted by a test and captured to docs/output/: the self-invocation trap, what CGLIB cannot override, the IllegalArgumentException a plain return type throws, the unbounded queue that makes max-size decoration, spring.task.execution.propagate-context (new in Boot 4.1.0), the two Executor beans that leave @Async on an unpooled SimpleAsyncTaskExecutor, and JEP 491 measured on JDK 21 against JDK 25.
This commit is contained in:
48
async/docs/03-what-the-proxy-cannot-see.md
Normal file
48
async/docs/03-what-the-proxy-cannot-see.md
Normal file
@@ -0,0 +1,48 @@
|
||||
prev: [The self-invocation trap](02-the-self-invocation-trap.md) · [README](../README.md) · next: [Return types and exceptions](04-return-types-and-exceptions.md)
|
||||
|
||||
# 3. What the proxy cannot see
|
||||
|
||||
Spring Boot proxies with CGLIB by default, so the proxy is a generated *subclass* of your bean:
|
||||
|
||||
```
|
||||
proxy class : com.ankurm.async.VisibilityService$$SpringCGLIB$$0
|
||||
isCglibProxy: true
|
||||
```
|
||||
|
||||
A subclass can override public and protected methods. It cannot override `final` ones, and it
|
||||
cannot see `private` ones. `@Async` on either is inert, and Spring does not complain.
|
||||
|
||||
From [`docs/output/visibility.txt`](output/visibility.txt):
|
||||
|
||||
```
|
||||
publicMethod() : task-3 (virtual=false)
|
||||
finalMethod() : main (virtual=false)
|
||||
protectedMethod() via the proxy : task-4 (virtual=false)
|
||||
callProtectedInternally() : main (virtual=false)
|
||||
```
|
||||
|
||||
Two things in that transcript are worth separating, because they look the same and are not:
|
||||
|
||||
- `finalMethod()` ran on `main` because **CGLIB could not override it**. Calling it from another
|
||||
bean would not help.
|
||||
- `callProtectedInternally()` ran on `main` because it is a **self-invocation** (chapter 2).
|
||||
`protectedMethod()` itself is perfectly proxyable, and running on `task-4` when the test calls
|
||||
it through the proxy proves it.
|
||||
|
||||
So "it ran on the caller's thread" has at least two distinct causes, and the fix differs. The
|
||||
test in `VisibilityTest` can call a protected method at all only because it lives in the same
|
||||
package as the service.
|
||||
|
||||
## The `final` class case
|
||||
|
||||
A `final` class cannot be subclassed either, so CGLIB cannot proxy it at all. That one *does*
|
||||
fail loudly — context startup throws — which makes it much less dangerous than a `final` method.
|
||||
|
||||
## Kotlin
|
||||
|
||||
Kotlin classes and members are `final` unless declared `open`. A Kotlin service with `@Async` and
|
||||
no `open` keyword, and no `kotlin-spring` compiler plugin, is the `final` case above. The
|
||||
`kotlin-spring` plugin exists to open Spring-annotated classes automatically; it does not open
|
||||
methods annotated only with `@Async` unless the class-level rule already applies.
|
||||
|
||||
next: [Return types and exceptions](04-return-types-and-exceptions.md)
|
||||
Reference in New Issue
Block a user