Which contexts can be refreshed more than once, and what are the close()/shutdown semantics?
answer
- Refreshable (XML/web) = new factory each refresh → reloadable
- Generic/AnnotationConfig = AtomicBoolean, single refresh, IllegalStateException
- close(): ContextClosedEvent → stop Lifecycle → destroySingletons → closeBeanFactory
- registerShutdownHook = close on JVM exit
- AutoCloseable → try-with-resources
basics
~10 sAbstractRefreshableApplicationContext subclasses (like ClassPathXmlApplicationContext) can call refresh() repeatedly to reload definitions; GenericApplicationContext (and AnnotationConfigApplicationContext) can only refresh once. close() stops lifecycle beans and runs destroy callbacks; registerShutdownHook ties that to JVM exit.
solid answer
~30 sThere are two families. AbstractRefreshableApplicationContext (parent of ClassPathXmlApplicationContext, FileSystemXmlApplicationContext, and the classic XmlWebApplicationContext) discards its old DefaultListableBeanFactory and rebuilds definitions on each refresh() — so it supports multiple refreshes / config reload. GenericApplicationContext keeps a single fixed bean factory and guards refresh() with an AtomicBoolean, so a second call throws IllegalStateException; AnnotationConfigApplicationContext inherits that single-refresh limit. Shutdown is symmetric to startup: close() (ConfigurableApplicationContext is AutoCloseable) publishes ContextClosedEvent, calls lifecycleProcessor.onClose() to stop Lifecycle/SmartLifecycle beans in reverse phase order, then destroySingletons() runs @PreDestroy/DisposableBean callbacks and closes the factory. registerShutdownHook() registers a JVM hook so close runs on process exit.
code
java · 10 lines// Refreshable: can reload definitions
var xml = new ClassPathXmlApplicationContext("beans.xml");
xml.refresh(); // rebuilds the bean factory from XML again — allowed
// Generic / annotation: single refresh only
var ac = new AnnotationConfigApplicationContext(AppConfig.class); // refresh() done in ctor
// ac.refresh(); // -> IllegalStateException (already refreshed)
ac.registerShutdownHook(); // close() (destroy callbacks) will run on JVM exit
ac.close(); // or explicit close via AutoCloseablego deeper
Know that close() shuts the context down and runs cleanup, and that some contexts refresh only once.
Distinguish refreshable XML contexts from single-refresh Generic/annotation contexts and describe basic close() cleanup.
Explain the AtomicBoolean single-refresh guard, the mirror-of-refresh close sequence, and registerShutdownHook / AutoCloseable.
Reason about reload strategies (recreate vs re-refresh), reverse-order destruction of resources, devtools/restart mechanics, and graceful-shutdown implications in production.
**Two context families, two refresh policies.** 1. **`AbstractRefreshableApplicationContext`** — subclasses: `ClassPathXmlApplicationContext`, `FileSystemXmlApplicationContext`, classic `XmlWebApplicationContext`, `AnnotationConfigWebApplicationContext`. On every `refresh()` its `refreshBeanFactory()` **closes any existing `DefaultListableBeanFactory`, creates a brand-new one, and reloads all bean definitions** from the configured sources. This makes them *refreshable*: calling `refresh()` again reloads configuration (historically used for config hot-reload). Each refresh destroys the previous singletons first. 2. **`GenericApplicationContext`** — holds one `DefaultListableBeanFactory` for its entire life. `refreshBeanFactory()` only flips an internal `AtomicBoolean refreshed` from false→true; if it's already true it throws `IllegalStateException('GenericApplicationContext does not support multiple refresh attempts: just call refresh once')`. `AnnotationConfigApplicationContext` extends `GenericApplicationContext`, so it too is **single-refresh**. This is why annotation-based contexts are effectively immutable once started. **Practical consequence:** if you need reloadable configuration you must use a refreshable subclass; you cannot 'reload' an `AnnotationConfigApplicationContext` — you close it and build a new one. (Spring Boot's Actuator `RestartEndpoint`/devtools work by tearing down and recreating the context, not by re-refreshing it.) **close() / shutdown semantics** (`AbstractApplicationContext.close()` → `doClose()`), the mirror of refresh: - Sets `active=false`, `closed=true`. - Publishes **`ContextClosedEvent`** so listeners can react. - Calls `getLifecycleProcessor().onClose()` → **stops `Lifecycle`/`SmartLifecycle` beans in reverse phase order** (graceful shutdown; phased stop detail is a lifecycle-topic concern, here just note close triggers it). - Calls **`destroyBeans()` / `destroySingletons()`** which invokes destruction callbacks (`DisposableBean.destroy()`, `@PreDestroy`, custom `destroy-method`) on singletons in reverse creation order. - Calls `closeBeanFactory()` to release the factory. **JVM integration:** `registerShutdownHook()` adds a `Runtime.getRuntime().addShutdownHook(...)` that calls `close()` on JVM termination, so destroy callbacks fire even if you never explicitly close. Spring Boot registers this automatically. **AutoCloseable:** `ConfigurableApplicationContext extends Closeable/AutoCloseable`, so try-with-resources cleanly closes the context. **Gotchas:** (a) A closed context cannot be reused — `getBean` after close throws. (b) On a refreshable context, an exception during a *re-refresh* can leave you without a usable factory; the original is already discarded. (c) Idempotency: calling `close()` twice is safe (guarded by the `closed` flag); calling `refresh()` again on a Generic context is not. (d) Destruction order matters for resources (datasource pools, executors) — Spring destroys in reverse dependency order to avoid using an already-closed dependency.
- Why can't you re-refresh an AnnotationConfigApplicationContext to reload beans?It extends GenericApplicationContext, which keeps one bean factory guarded by an AtomicBoolean; a second refresh() throws IllegalStateException. To reload you must close it and create a new context, which is how devtools/restart works.
- What runs during close() and in what order relative to startup?close() is the mirror of refresh: publish ContextClosedEvent, stop Lifecycle/SmartLifecycle beans (reverse phase order), then destroySingletons() runs @PreDestroy/DisposableBean callbacks in reverse creation order, then the bean factory is closed.
saying these in an interview costs you the question
- Claiming AnnotationConfigApplicationContext can be refreshed repeatedly
- Saying close() is optional with no effect (it runs destroy callbacks and stops lifecycle beans)
- Assuming destroy order is the same as creation order (it's reverse)
- Believing a closed context can be reopened/reused