Add core-events module: @EventListener, @TransactionalEventListener phases and async listeners on Boot 4.1

Thirteen captured transcripts: a listener is a blocking method call, ordering and chaining, SpEL conditions,
generic-event erasure, every transaction phase on commit and rollback, what an AFTER_COMMIT listener can
write, which exceptions reach the publisher, async listeners on platform and virtual threads, and the
annotations behind Spring Modulith's @ApplicationModuleListener.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
This commit is contained in:
Claude
2026-09-24 07:08:47 +00:00
parent c418589251
commit d6b76c57db
72 changed files with 1658 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# core-events
Companion project for the article **[Spring Application Events: @EventListener, @TransactionalEventListener and Async Events](https://ankurm.com/spring-application-events-eventlistener-transactionaleventlistener-async/)** on **[ankurm.com](https://ankurm.com)**.
Every console block quoted in the article came out of `output/`. Transcripts 01-11 are written by the test
suite (so a claim that stops being true turns the build red); 12-13 are read out of jars by
`scripts/capture-facts.sh`.
There is deliberately **no `docs/` folder**: the deeper material lives in collapsible "going deeper"
sections inside the article itself, next to the paragraph each one extends.
## Versions
| | |
|---|---|
| Spring Boot | 4.1.1 |
| Spring Framework | 7.0.9 |
| JDK | 25 (Temurin 25.0.4.1+1) |
| Maven | 3.9 |
| H2 | the version Boot 4.1.1 manages |
| Spring Modulith (javap only) | 2.1.1 |
## Quickstart
```bash
export JAVA_HOME=/path/to/jdk-25
mvn test # runs the scenarios and rewrites output/01-11
./scripts/run-all.sh # everything, including 12-13 (needs network access to Maven Central once)
```
## Source layout
Each scenario is its own package with its own `@SpringBootApplication`, which scans only that package, so a
scenario starts with exactly the beans it shows and nothing else.
| Package | What it holds |
|---|---|
| `basics/` | `OrderService` publishes an `OrderPlaced` record; two listeners; the publisher blocks |
| `ordering/` | `@Order` across two beans, an event returned from a listener, a listener that throws |
| `conditional/`, `badcondition/` | SpEL `condition`s, and one that names a property that does not exist |
| `generics/` | `Created<T>` and its erasure problem, and `ResolvableTypeProvider` |
| `tx/` | one listener per transaction phase, a publisher that commits or rolls back, `fallbackExecution` |
| `txwrite/` | an `AFTER_COMMIT` listener that writes: nothing declared, `REQUIRES_NEW`, plain `@Transactional` |
| `asyncdemo/` | `@Async` listeners, the thread they land on, and what they see of the publisher's transaction |
| `asyncplain/` | the same `@Async` listener in an application that forgot `@EnableAsync` |
| `support/` | `Trace` (the in-memory log the tests print), `Modes`, and `Visibility` (asks "could another connection see this row?") |
## Captured output
| File | What it shows |
|---|---|
| `01-publish-is-a-method-call.txt` | a listener runs on the publisher's thread, before `publishEvent` returns |
| `02-order-and-chaining.txt` | `@Order` across beans, an event returned from a listener, and a throwing listener stopping the rest |
| `03-conditional-listeners.txt` | which SpEL-conditioned listeners ran for four events; the failure for a misspelled property |
| `04-generic-events-and-erasure.txt` | a generic event that reaches no listener, and the `ResolvableTypeProvider` fix |
| `05-transaction-phases-commit-and-rollback.txt` | every phase for a commit and for a rollback, with row visibility from another connection |
| `06-no-transaction-drops-the-event.txt` | publishing outside a transaction, and `fallbackExecution` |
| `07-after-commit-writes.txt` | an `AFTER_COMMIT` listener that inserts, declared three ways |
| `08-exceptions-and-transactions.txt` | which listener's exception reaches the caller, and what it does to the row |
| `09-async-listener-threads.txt` | an `@Async` listener on a platform thread, on a virtual thread, and with `@EnableAsync` forgotten |
| `10-async-and-the-transaction.txt` | what an async listener can see of the publisher's transaction |
| `11-async-exceptions.txt` | an async listener's exception: not the publisher's problem, but logged |
| `12-modulith-application-module-listener.txt` | the annotations `@ApplicationModuleListener` is made of |
| `13-transaction-cleanup-calls.txt` | the calls `DataSourceTransactionManager` makes when it cleans up |