Appearance
Bean Scopes: Singleton, Prototype, Request and Session
Chapter 6 showed the lifecycle of a bean, and chapter 7 how beans are injected into each other. Every bean so far existed exactly once. The scope of a bean decides:
- how many objects Spring creates for it, and
- when it creates them (at startup, or later).
The lifecycle from chapter 6 (construct, inject, @PostConstruct, use, @PreDestroy) is the same for every scope; the scope only decides when step 3, construct, happens and how often.
| Scope | How many objects | Created |
|---|---|---|
| singleton (default) | one per IoC container | eagerly, at startup |
| prototype | a new one every time one is needed | lazily, when needed |
| request | one per HTTP request | lazily, during a request |
| session | one per HTTP session | lazily, during a session |
| application | one per servlet context, across containers | rarely used |
Every experiment below uses the same small classes, which print when they are created and print their hashCode(). Equal hash codes mean the same object; a different hash code means a new object.
Scope 1: singleton (the default)
One object per IoC container. Every bean that depends on it receives the same object. In a normal application, with one container, that means one object for the whole application.
There are three ways to write it; all mean the same:
java
@Component // no @Scope: singleton is the default
public class User { }
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // the constant
public class User { }
@Component
@Scope("singleton") // the plain string
public class User { }The experiment
java
@Component
@Scope("singleton")
public class User {
public User() {
System.out.println("User initialization");
}
@PostConstruct
public void init() {
System.out.println("User object hashCode: " + this.hashCode());
}
}Two controllers depend on it:
java
@RestController
@RequestMapping("/api")
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class TestController1 {
@Autowired
User user;
public TestController1() {
System.out.println("TestController1 instance initialization");
}
@PostConstruct
public void init() {
System.out.println("TestController1 object hashCode: " + this.hashCode()
+ ", user object hashCode: " + user.hashCode());
}
@GetMapping("/fetchUser1")
public String fetchUser() {
return "ok";
}
}TestController2 is the same, with 2 in the names.
Start the application:
text
User initialization
User object hashCode: 287859212
TestController2 instance initialization
TestController2 object hashCode: 229329107, user object hashCode: 287859212
TestController1 instance initialization
TestController1 object hashCode: 1901700557, user object hashCode: 287859212- All three beans were created at startup: singletons are eager.
Userwas created once, and both controllers hold the sameUser(287859212).
Call GET /api/fetchUser1: nothing is printed. No object is created on a request; everything was built at startup and is reused.
Use it for stateless beans: services, repositories, controllers, clients. Almost every bean in an application is a singleton.
Scope 2: prototype
A new object every time one is needed, and lazy: not created at startup unless something created at startup needs it.
java
@Component
@Scope("prototype") // or ConfigurableBeanFactory.SCOPE_PROTOTYPE
public class User { ... }The experiment
User: prototype.Student: singleton, depends onUser.TestController1: prototype, depends onUserandStudent.
java
@Component
public class Student {
@Autowired
User user;
public Student() {
System.out.println("Student instance initialization");
}
@PostConstruct
public void init() {
System.out.println("Student object hashCode: " + this.hashCode()
+ ", user object hashCode: " + user.hashCode());
}
}
@RestController
@RequestMapping("/api")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TestController1 {
@Autowired
User user;
@Autowired
Student student;
// constructor and @PostConstruct print as before
@GetMapping("/fetchUser")
public String fetchUser() {
return "ok";
}
}Start the application:
text
Student instance initialization
User initialization
User object hashCode: 118363130
Student object hashCode: 1604020967, user object hashCode: 118363130TestController1is prototype, so it is not created at startup.Studentis a singleton, so it is created. It needs aUser;Useris prototype, but needed now, so one is created forStudent.
Call GET /api/fetchUser:
text
TestController1 instance initialization
User initialization
User object hashCode: 1641704889
TestController1 object hashCode: 1787122392, user: 1641704889, student: 1604020967- To handle the request, Spring needs a
TestController1; it is prototype, so a new one is created. - It needs a
User: prototype, so a newUser(1641704889), not the oneStudentholds. - It needs a
Student: singleton, so the existing one (1604020967).
Call it again:
text
TestController1 instance initialization
User initialization
User object hashCode: 1351059270
TestController1 object hashCode: 1581282086, user: 1351059270, student: 1604020967Another new controller, another new User, the same Student.
A trap: Student is a singleton, so it is created once and its User is injected once. It keeps that same User forever; it does not get a new prototype each time it uses it. To get a fresh prototype on each use from inside a singleton, ask the container each time, for example through an ObjectProvider<User> (provider.getObject()).
Use it for objects that hold state which must not be shared: a builder, a form being filled, a per-task worker.
Scope 3: request
One object per HTTP request. Within one request, every bean that needs it gets the same object; the next request gets a new one. It is lazy: it can only exist while a request is being handled.
java
@Component
@Scope("request")
public class User { ... }The experiment
TestController1: request scope, depends onUserandStudent.User: request scope.Student: prototype, depends onUser.
Start the application: nothing is printed. All three are lazy, and no request exists yet.
Call GET /api/fetchUser:
text
TestController1 instance initialization
User initialization
User object hashCode: 633462915
Student instance initialization
Student object hashCode: 1501643322, user object hashCode: 633462915
TestController1 object hashCode: 1166294723, user: 633462915, student: 1501643322- A request arrived, so a
TestController1is created for it. - It needs a
User: request scope, and none exists for this request yet, so one is created (633462915). - It needs a
Student: prototype, so a new one.Studentneeds aUser, and this request already has one: it gets the sameUser(633462915), not a new one.
Call it again:
text
TestController1 instance initialization
User initialization
User object hashCode: 1351059270
Student instance initialization
Student object hashCode: 1581282086, user object hashCode: 1351059270
TestController1 object hashCode: 1518332982, user: 1351059270, student: 1581282086A new request: a new controller, a new User (1351059270), shared again by the controller and the Student within this request.
Use it for data that belongs to one request: the current user's id taken from a header, a request id for logging, per-request caches.
The problem: a singleton that needs a request-scoped bean
Now make the controller a singleton (the default) while User stays request-scoped:
java
@Component
@Scope("request")
public class User { ... }
@RestController
@RequestMapping("/api")
public class TestController1 { // singleton: created at startup
@Autowired
User user;
...
}Start the application. It fails:
text
TestController1 instance initialization
...
Error creating bean with name 'user': Scope 'request' is not active for the current thread;
consider defining a scoped proxy for this bean if you intend to refer to it from a singletonFollow the lifecycle: the singleton TestController1 is created at startup and needs a User injected now. But a request-scoped User belongs to an HTTP request, and at startup there is no request. Spring cannot create it, so the controller cannot be completed, and startup fails.
The fix: a scoped proxy
The error message says what to do: define a scoped proxy.
java
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class User { ... }proxyMode = ScopedProxyMode.TARGET_CLASS tells Spring: when someone needs this bean and no request is active, inject a stand-in. The proxy is a generated subclass of User. At startup it is injected into the controller in place of a real User. Later, every time a method is called on it, the proxy looks up the real User of the current request (creating it if needed) and forwards the call.
Add a print to the handler method (System.out.println("fetchUser called, user = " + user)), then start the application:
text
TestController1 instance initialization
TestController1 object hashCode: 1336762904, user: 943044301Startup succeeds. No User initialization line: the controller received the proxy, not a real User.
Call GET /api/fetchUser twice:
text
User initialization
User object hashCode: 1761435935
fetchUser called, user = com.example.demo.User@68fd611f
User initialization
User object hashCode: 2001450859
fetchUser called, user = com.example.demo.User@774bb76bThe controller is still the one singleton, holding the one proxy, but each request now gets its own real User, created when the proxy is first used in that request.
Shortcuts. @RequestScope (and @SessionScope below) are request (session) scope with proxyMode = TARGET_CLASS already set:
java
@Component
@RequestScope // = @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class User { ... }Scope 4: session
One object per HTTP session. A session lasts across many requests from the same user (the browser sends its session cookie back each time) until it expires or is invalidated, for example by logging out. It is lazy, like request scope.
The session timeout can be set in application.properties, for example server.servlet.session.timeout=30m.
The experiment
TestController1: session scope, depends onUser.User: singleton.- A
/logoutendpoint ends the session by hand:
java
@RestController
@RequestMapping("/api")
@Scope("session")
public class TestController1 {
@Autowired
User user;
// constructor and @PostConstruct print as before
@GetMapping("/fetchUser")
public String fetchUser() {
return "ok";
}
@GetMapping("/logout")
public String logout(HttpServletRequest request) {
HttpSession session = request.getSession();
session.invalidate(); // end this session
return "logged out";
}
}| Step | Log | Why |
|---|---|---|
| start the application | User initialization, User object hashCode: 1010983633 | User is a singleton: eager. The session-scoped controller is not created. |
GET /api/fetchUser | TestController1 instance initialization, ... user: 1010983633 | first request creates a session, and a controller for it |
GET /api/fetchUser again | (nothing) | same session, so the same controller is reused |
GET /api/logout | (nothing) | the session is invalidated |
GET /api/fetchUser | TestController1 instance initialization, ... user: 1010983633 | new session, new controller; still the same singleton User |
(The second call reuses the session because the client sends the session cookie back. A client that drops cookies starts a new session on every request.)
A singleton that needs a session-scoped bean has the same startup problem as with request scope, with the same fix: proxyMode = ScopedProxyMode.TARGET_CLASS, or @SessionScope.
Use it for per-user state across requests: a shopping cart, a multi-step form, user preferences.
Application scope
Application scope means one object per servlet context, shared even by several IoC containers running in the same web application, where a singleton is one per container. Applications almost always run a single container, so in practice it behaves like a singleton and is rarely used.
Summary
| singleton | prototype | request | session | |
|---|---|---|---|---|
| Objects | 1 per container | 1 per injection / lookup | 1 per HTTP request | 1 per HTTP session |
| Created | at startup | when needed | during a request | during a session |
| Into a singleton | fine | injected once, then kept | needs a scoped proxy | needs a scoped proxy |
| Typical use | services, repositories, controllers | stateful helpers | per-request data | per-user data |
Next: Chapter 7 chose between OnlineOrder and OfflineOrder with @Qualifier, so changing the choice meant changing code. Chapter 9 starts the configuration module: it moves that choice into application.properties with @Value.
Check yourself
- What two things does a bean's scope decide?
- What is the default scope? When is such a bean created, and how many exist?
- In the prototype experiment, why is a
Usercreated at startup althoughUseris prototype? - A singleton
Studentdepends on a prototypeUser. DoesStudentget a newUsereach time it uses it? How could it? - Within one HTTP request, the controller and a prototype
Studentboth need a request-scopedUser. How manyUserobjects are created? - A singleton controller injects a bean with
@Scope("request"). What happens at startup, and why? - What does
proxyMode = ScopedProxyMode.TARGET_CLASSinject, and what happens when a method is called on it? - What is the difference between
@RequestScopeand@Scope("request")? - A session-scoped controller: when is a new instance created, and when is an existing one reused?
- What is the difference between singleton and application scope?
Answers
- How many objects are created, and when.
- Singleton: created eagerly at startup; one per IoC container.
- The singleton
Studentis created at startup and needs aUser, so one is created for it then. - No: it was injected once and kept. Use
ObjectProvider<User>(or another lookup in the container) to get a new one on each use. - One: request scope means one per request, shared by everything that needs it during that request.
- Startup fails ("Scope 'request' is not active for the current thread"): the singleton is built at startup, when there is no HTTP request to create the request-scoped bean for.
- A proxy, a generated subclass that stands in for the bean. Each method call looks up (or creates) the real object for the current request or session and forwards the call.
@RequestScopealso setsproxyMode = TARGET_CLASS; plain@Scope("request")has no proxy.- A new one for each new session (the first request, or the first request after the session ends, for example after logout); the same one for every request within a session.
- Singleton: one per IoC container. Application: one per servlet context, shared even across containers. With one container, they behave the same.