Card 05/ 07
Request-Scoped Beans in a Singleton: Why a Scoped Proxy Is Needed
Without a proxy the application cannot start, because no web request exists while the singleton is built.
A request-scoped bean has one instance per web request, and a singleton is built once, at startup, before any request exists. Injecting the first directly into the second fails, and the fix is a scoped proxy: a stand-in object that looks up the current request's instance on every call.
CurrentRideContext is scoped to one request and holds the ride that request is about. RideAuditor is a singleton that depends on it, wired the obvious way through its constructor:
@Component
@Scope("request")
class CurrentRideContext {
private final String rideId = currentRideId();
String describe() { return "handling " + rideId; }
}
@Component
class RideAuditor {
private final CurrentRideContext currentRideContext;
RideAuditor(CurrentRideContext currentRideContext) { this.currentRideContext = currentRideContext; }
void audit() { System.out.println(Thread.currentThread().getName() + " sees: " + currentRideContext.describe()); }
}RideAuditor is built once, at startup, and building it means resolving its constructor argument at that moment. There is no web request at startup, so there is no CurrentRideContext to give it, and the application fails to start with an error saying that scope 'request' is not active for the current thread. The error message itself suggests the fix: consider defining a scoped proxy for this bean.
The fix is declared with proxyMode = ScopedProxyMode.TARGET_CLASS on the request-scoped bean's definition. RideAuditor then receives a proxy instead of a real instance, and on every call the proxy finds the CurrentRideContext that belongs to the request running at that moment:
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
CurrentRideContext currentRideContext() { return new CurrentRideContext(); }A related case fails silently instead. A prototype bean, one Spring creates afresh every time it is asked for, injected into a singleton is created once, when the singleton is built, and the singleton keeps that one instance for the rest of the application's life.
So whenever a bean with a shorter life is injected into a longer-lived one, ask when the longer-lived one is built. If the shorter-lived bean may not exist yet, or should change afterwards, inject a scoped proxy.
← → move through sections and cards