# core-beans Companion project for two articles on **[ankurm.com](https://ankurm.com)**. | Article | What it demonstrates | |---|---| | [Spring Bean Scopes: Singleton, Prototype, Request, Session and the Prototype-in-Singleton Trap](https://ankurm.com/spring-bean-scopes-singleton-prototype-request-session-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](https://ankurm.com/spring-bean-lifecycle-postconstruct-smartlifecycle-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 | 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 ```bash 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 | ## 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 (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`](output/01-instance-counts.txt) | How many instances did the container really construct? | | [`02-prototype-destroy.txt`](output/02-prototype-destroy.txt) | @PreDestroy on a singleton and on a prototype, then context.close() | | [`03-prototype-in-singleton.txt`](output/03-prototype-in-singleton.txt) | A singleton calls use() five times. How many prototype instances did it touch? | | [`04-singleton-thread-safety.txt`](output/04-singleton-thread-safety.txt) | Two threads call the same singleton; a latch forces the interleaving | | [`05-web-scopes.txt`](output/05-web-scopes.txt) | request, session and application scope over real HTTP | | [`06-request-scope-without-proxy.txt`](output/06-request-scope-without-proxy.txt) | A request-scoped bean (no proxy) injected into a singleton | | [`07-full-callback-order.txt`](output/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`](output/08-non-static-bpp-warning.txt) | A BeanPostProcessor declared with a non-static @Bean method | | [`09-postconstruct-before-proxy.txt`](output/09-postconstruct-before-proxy.txt) | @PostConstruct runs before the @Async proxy exists | | [`10-postconstruct-failure.txt`](output/10-postconstruct-failure.txt) | An exception thrown from @PostConstruct | | [`11-smartlifecycle-phases.txt`](output/11-smartlifecycle-phases.txt) | Three SmartLifecycle beans registered in the order 300, 100, 200 | | [`12-blocking-vs-async-stop.txt`](output/12-blocking-vs-async-stop.txt) | Three SmartLifecycle beans in the SAME phase, each needing 400 ms to stop | | [`13-plain-lifecycle.txt`](output/13-plain-lifecycle.txt) | Lifecycle vs SmartLifecycle: who starts at refresh()? | | [`14-shutdown-timeout.txt`](output/14-shutdown-timeout.txt) | A SmartLifecycle whose stop(callback) never calls the callback, timeout 500 ms | | [`15-smartlifecycle-beans-in-boot.txt`](output/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`](output/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`](output/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`](output/18-property-defaults.txt) | Property defaults read from spring-configuration-metadata.json (Boot 4.1.1 jars) | | [`19-bean-post-processors.txt`](output/19-bean-post-processors.txt) | The BeanPostProcessors registered in a plain Spring Boot context, in the order they run | ## Licence MIT, see the repository root.