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
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Facts that come from jars rather than from running the scenarios:
# 12 what Spring Modulith's @ApplicationModuleListener is made of (read with javap)
# 13 what DataSourceTransactionManager does when a transaction is cleaned up (read with javap)
set -euo pipefail
cd "$(dirname "$0")/.."
mkdir -p output target/facts
M2=~/.m2/repository
MODULITH=2.1.1 # newest GA in maven-metadata.xml at the time of writing; 2.2.0-M1 is a milestone
fetch() { # url, destination: Maven Central rate-limits shared addresses, so retry politely
for i in 1 2 3 4 5 6; do
code=$(curl -s -o "$2" -w '%{http_code}' "$1" || true)
[ "$code" = 200 ] && return 0
sleep $((i * 10))
done
echo "could not fetch $1 (last status $code)" >&2; return 1
}
JAR=target/facts/spring-modulith-events-api-$MODULITH.jar
[ -s "$JAR" ] || fetch "https://repo1.maven.org/maven2/org/springframework/modulith/spring-modulith-events-api/$MODULITH/spring-modulith-events-api-$MODULITH.jar" "$JAR"
SPRING=7.0.9
CP="$JAR"
for a in spring-core spring-context spring-tx spring-aop spring-beans; do
CP="$CP:$(find "$M2/org/springframework/$a/$SPRING" -name "$a-$SPRING.jar" | head -1)"
done
{
echo "# spring-modulith-events-api $MODULITH: the annotations that @ApplicationModuleListener carries (reflection)"
echo
java -cp "$CP" scripts/facts/PrintAnnotations.java org.springframework.modulith.events.ApplicationModuleListener 2>/dev/null \
| grep -E 'Async|Transactional'
} > output/12-modulith-application-module-listener.txt
cat output/12-modulith-application-module-listener.txt
SPRINGJDBC=$(find "$M2/org/springframework/spring-jdbc/7.0.9" -name 'spring-jdbc-7.0.9.jar' | head -1)
rm -rf target/facts/jdbc && mkdir -p target/facts/jdbc
unzip -o -q "$SPRINGJDBC" 'org/springframework/jdbc/datasource/DataSourceTransactionManager.class' -d target/facts/jdbc
{
echo "# DataSourceTransactionManager.doCleanupAfterCompletion in spring-jdbc 7.0.9: the calls it makes (javap -c)"
echo
javap -c -p -cp target/facts/jdbc org.springframework.jdbc.datasource.DataSourceTransactionManager \
| awk '/void doCleanupAfterCompletion/,/resetConnectionAfterTransaction/' | grep -E 'invoke' | sed -E 's/^.*\/\/ (Interface)?Method //'
} > output/13-transaction-cleanup-calls.txt
cat output/13-transaction-cleanup-calls.txt
@@ -0,0 +1,11 @@
import java.lang.annotation.Annotation;
/** Prints the annotations a class carries, as the JVM reports them. Usage: java PrintAnnotations.java <class name> */
public class PrintAnnotations {
public static void main(String[] args) throws Exception {
Class<?> type = Class.forName(args[0]);
for (Annotation a : type.getAnnotations()) {
System.out.println(a);
}
}
}
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Regenerates every file under output/.
#
# ./scripts/run-all.sh
#
# Needs a JDK 25 and Maven 3.9. Transcripts 01-11 are written by the test suite, so each figure in the
# article is an assertion that fails the build if it stops being true. Transcripts 12-13 come from javap.
set -euo pipefail
cd "$(dirname "$0")/.."
echo "== test suite (transcripts 01-11)"
mvn -B test
echo "== facts read from jars (12-13)"
./scripts/capture-facts.sh
echo
echo "output:"
ls -1 output