Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
9.2 KiB
9.2 KiB
core-beans
Companion project for three articles on ankurm.com.
| Article | What it demonstrates |
|---|---|
| Spring Bean Scopes: Singleton, Prototype, Request, Session and the Prototype-in-Singleton Trap | instance counts for every scope, five ways to get a fresh prototype inside a singleton, a singleton that leaks one caller's data into another's, and request scope without a proxy failing at start-up |
| Spring Bean Lifecycle in Boot 4: @PostConstruct, InitializingBean, SmartLifecycle and Shutdown Order | every callback in order from a real run, @PostConstruct running before the proxy exists, the non-static BeanPostProcessor warning, SmartLifecycle phases measured, and a request in flight during graceful shutdown with virtual threads |
| Circular Dependencies in Spring Boot 4: Why Startup Fails and 4 Ways to Fix It | the start-up failure report for a three-service ring, the creation order behind it, what allow-circular-references does and does not repair (a half-built bean, @Async versus @Cacheable, an INFO line that swallows an exception), and four fixes run side by side: redesign, events, @Lazy, ObjectProvider |
Every console block, exception message and count quoted in those articles came out of output/,
and every file there is regenerated by one script. Most are written by the test suite, so if a
claim stops being true the build goes red.
There is deliberately no docs/ folder: the deeper material lives in collapsible "going
deeper" sections inside the articles themselves, 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 |
Quickstart
export JAVA_HOME=/path/to/jdk-25
mvn test # runs the scenarios and rewrites the test-written files in output/
./scripts/run-all.sh # everything, including the script-captured files
Source layout
| Package | What it holds |
|---|---|
scopes/ |
singleton, @Lazy, prototype, scoped-prototype and the five ways a singleton can obtain a prototype; the shared-state greeter |
web/ |
@RequestScope, @SessionScope, @ApplicationScope beans and a diagnostic /scopes controller, plus the un-proxied request bean that fails at start-up |
lifecycle/ |
KitchenSink (every callback), the post-processor traps, PhasedWorker and NeverStops for SmartLifecycle |
shutdown/ |
a web application with a /slow endpoint and two SmartLifecycle beans at different phases |
cycles/ |
one package per scenario: broken/ (the three-service ring), allowed/ (field cycles with the flag on), redesign/, events/, lazy/ and provider/ (the four fixes), beanmethods/ (a cycle written as @Bean methods) |
Endpoints
Both endpoints exist only to be called by the tests. They have no authorisation. Delete them before shipping.
| Endpoint | Application | Purpose |
|---|---|---|
GET /scopes |
WebScopesApp |
request, session and application instance serials, and the injected proxy class |
GET /slow?ms=1500 |
ShutdownApp |
sleeps, so a request can be in flight when the context closes |
Captured output
Files 01-17 and 19-35 (tests) and 18 (capture-metadata.sh). Timing rows assert coarse thresholds, not exact milliseconds; treat them as indicative.
| File | What it shows |
|---|---|
01-instance-counts.txt |
How many instances did the container really construct? |
02-prototype-destroy.txt |
@PreDestroy on a singleton and on a prototype, then context.close() |
03-prototype-in-singleton.txt |
A singleton calls use() five times. How many prototype instances did it touch? |
04-singleton-thread-safety.txt |
Two threads call the same singleton; a latch forces the interleaving |
05-web-scopes.txt |
request, session and application scope over real HTTP |
06-request-scope-without-proxy.txt |
A request-scoped bean (no proxy) injected into a singleton |
07-full-callback-order.txt |
Every callback for one bean, from constructor to the last destroy hook (SpringApplication, no web server) |
08-non-static-bpp-warning.txt |
A BeanPostProcessor declared with a non-static @Bean method |
09-postconstruct-before-proxy.txt |
@PostConstruct runs before the @Async proxy exists |
10-postconstruct-failure.txt |
An exception thrown from @PostConstruct |
11-smartlifecycle-phases.txt |
Three SmartLifecycle beans registered in the order 300, 100, 200 |
12-blocking-vs-async-stop.txt |
Three SmartLifecycle beans in the SAME phase, each needing 400 ms to stop |
13-plain-lifecycle.txt |
Lifecycle vs SmartLifecycle: who starts at refresh()? |
14-shutdown-timeout.txt |
A SmartLifecycle whose stop(callback) never calls the callback, timeout 500 ms |
15-smartlifecycle-beans-in-boot.txt |
Every SmartLifecycle bean in a Boot web application, highest phase (stops first) at the top |
16-graceful-shutdown-in-flight.txt |
A /slow?ms=1500 request is in flight when the context closes |
17-worker-phase-vs-web-server.txt |
SmartLifecycle beans with the default phase and with phase 1000, while a request is in flight |
18-property-defaults.txt |
Property defaults read from spring-configuration-metadata.json (Boot 4.1.1 jars) |
19-bean-post-processors.txt |
The BeanPostProcessors registered in a plain Spring Boot context, in the order they run |
20-boot-cycle-failure-report.txt |
Three constructor-injected services in a ring: what Spring Boot prints when start-up fails |
21-cycle-exception-and-creation-order.txt |
The exception behind the report, and the order in which the container started creating beans |
22-bean-method-cycle-failure-report.txt |
A two-bean cycle written as @Bean methods |
23-lazy-initialization-hides-the-cycle.txt |
spring.main.lazy-initialization=true on the same ring: it starts, then fails on first use |
24-allow-circular-half-built-bean.txt |
A field cycle with the flag off and on: what each @PostConstruct sees |
25-allow-circular-async-raw-reference.txt |
The same cycle with @Async: repaired silently at start-up, a raw-version error under lazy initialisation |
26-allow-circular-cacheable-early-proxy.txt |
The same cycle with @Cacheable: the injected reference is the proxy |
27-fix-redesign.txt |
Fix 1: extract the one thing the second bean needed |
28-fix-events.txt |
Fix 2: an event instead of a call, and what the publisher sees when the listener throws |
29-fix-lazy.txt |
Fix 3: @Lazy on the parameter, and the proxy class that arrives |
30-fix-object-provider.txt |
Fix 4: ObjectProvider, resolving in the constructor, and a final class |
31-lazy-on-a-final-class.txt |
@Lazy on a parameter whose type is a final class |
32-lazy-injection-point-versus-lazy-bean.txt |
@Lazy on the injection point versus on the bean, with a target that cannot be built |
33-dispatcher-handler-cycle.txt |
A cycle through List<Handler> and its ObjectProvider repair |
34-depends-on-cycle.txt |
@DependsOn in both directions: no injection involved |
35-lazy-used-during-construction.txt |
@Lazy on the parameter, but the bean calls the dependency from @PostConstruct |
36-main-property-defaults.txt |
The defaults of spring.main.allow-circular-references and spring.main.lazy-initialization, from Boot's own metadata |
37-early-reference-bytecode.txt |
Which post-processors hand out an early reference, and the BeanCurrentlyInCreationException handler in preInstantiateSingleton |
Licence
MIT, see the repository root.