Introduction
In the previous articles we have seen the Dependency Injection framework of Spring Boot and why it is required. We have seen the different ways in which we can inject dependencies into our Spring Boot application – via Constructor, Setter or via Fields. We have also seen how to dynamically inject dependencies, for cases when the dependencies need to be resolved dynamically.
Now that we have understood that the Dependency Injection Container helps in externalizing and managing the objects and helps decouple application components, we must ask a question – how does the Dependency Injection framework know when to dispose of the created objects? Since application memory is limited, once in a while it is necessary to garbage collect the unwanted objects created in memory and make the memory available for others to be created when needed.
But how does the Container know until what time it needs to keep a particular bean object in memory and when to dispose of it?
In this article, we will discuss Bean Scopes, which is the key to understanding how a Container determines the lifetime of a bean object and how it determines if an object needs to be removed or kept.
What are Bean Scopes?
In a nutshell, bean scopes are identifiers that define the lifetime of a bean created in a Spring Boot application. With the help of these bean scopes, we instruct the Container to retain a particular bean for certain scope, while removing others. When the scope of a particular bean is complete, the Container removes the object from the memory.
When a component at a later point of time requests an object of a particular bean, the Container checks if there is already an object available of that bean with a valid scope and reuses it. Otherwise it creates a new object of that bean type and returns it to the requesting component, while maintaining it until its scope expires.
Types of Bean Scopes in Spring Boot Dependency Injection
In Spring Boot Dependency Injection, there are different types of bean scopes available which define the lifetime of a bean object. These are as follows –
Singleton
If a Bean is marked as Singleton, one single instance of the bean is created per Spring container and is shared across the container. These beans are created when the Spring Container is initialized and are destroyed only when the Container is destroyed.
Singleton is the default Bean scope for all the beans created in Spring Boot, unless configured. Since the bean object is created only once and shared across the container, it is ideal for Stateless Beans, Repositories, Configurations, Business Logic, Services and DAOs.
@Service
public class AuthService {
}
Prototype
The opposite of a Singleton bean scope is Prototype scope. If a Bean is marked as Prototype, then a new instance of the Bean is returned each time a component requests for an object of that Bean. Since a new object is created once per request, the object is not managed by the Container and is Garbage Collected once it is no longer needed. Since it is short-lived and ephemeral in a way, it best suits for Stateful beans and objects that require unique instances.
@Service
@Scope("prototype")
public class ReportService {
}
Request
A Bean with Request scope is tied to a HTTP Request. All the beans that are created with scope as Request are shared across the container within a HTTP Request and are destroyed once the HTTP Request is completed. You can say that Beans with Request scope live only during the request lifecycle and are tied to the HttpServletRequest. Since it is tied to a request, these beans can be used for request specific functionalities such as form validations, parameter handling etc.
@Service
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserRequestService {
}
Session
A Bean with Session scope is tied to a HTTP Session. All the beans that are created with scope as Session are shared across the container within a user session and are destroyed once the session is complete. You can say that these beans are kind of tied to the HttpSession object of the user session. Since it is specific to a session, these beans can be used for session specific functionalities such as authentication or other user related details and validation.
@Service
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSessionService {
}
Application
A Bean with application scope is created and shared across an application scope. It is almost similar to a Singleton scope, except that with an application scope a bean is shared across all available Containers within that Servlet context.
A ServletContext is created and shared across all servlets living on the same servlet container (eg. a Tomcat server). ApplicationContext represents an IoC Container.
A single Servlet Container can contain multiple IoC Containers. Beans with application scope are bound to the ServletContext, while Beans with Singleton scope are bound to the ApplicationContext.
Hence you can have multiple Singleton beans of the same type (created across different IoC Containers) but you can have a single shared application scoped bean of a type. But in general, there will be only one IoC container created per Servlet Container.
Hence most of the time, both Application and Singleton Scoped beans have similar lifetimes.
@Component
@Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ApplicationBean {
public ApplicationBean() {
System.out.println("ApplicationBean instance created: " + this);
}
}
WebSocket
A Bean with web socket scope is tied to a web socket session, which is used in the context of websocket based communications. These beans are shared across a websocket session and are destroyed once the websocket session is completed. It is best suited for beans that maintain real-time, session based state.
@Component
@Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketBean {
public WebSocketBean() {
System.out.println("New WebSocketBean instance created: " + this);
}
public String getMessage() {
return "This bean exists for the WebSocket session!";
}
}
Injecting Beans with different Scopes
Now that we have seen how each of the Bean scopes are different from one another, it is also important to understand how we are injecting services keeping their scopes in mind. For example, the following injections can work directly without any issues –
- Singleton into a Prototype
- Singleton into a Request
- Request into a Prototype
But the following will not work directly –
- Prototype into a Singleton
- Prototype into a Request
- Request into a Singleton
Which means that beans with shorter scopes can’t be injected directly into beans with longer scopes because they are shared for longer periods, but by which the beans with shorter scopes may have been destroyed or are deemed invalid.
In such situations, we don’t inject the beans directly and instead use ObjectProvider to resolve an instance of a bean dynamically.
For example, let’s say we want to inject a UserRequestService that maintains a single user request id, into an AuthService, which is a singleton bean.
In this case, we can’t inject UserRequestService directly into AuthService. Instead we inject an ObjectProvider of type UserRequestService into the AuthService. This is done as below –
@Service
@Scope(value = "request")
public class UserRequestService {
private final String userId = UUID.randomUUID().toString();
@Autowired
HttpServletRequest request;
public String getUserId() {
return userId;
}
public String getRequestInfo() {
String clientIP = request.getRemoteAddr();
String userAgent = request.getHeader("User-Agent");
String requestURI = request.getRequestURI();
return "Client IP: " + clientIP + ", User-Agent: " + userAgent + ", URI: " + requestURI;
}
}
@Service
public class AuthService {
private final ObjectProvider<UserRequestService> userRequestServiceProvider;
@Autowired
public AuthService(ObjectProvider<UserRequestService> userRequestServiceProvider) {
this.userRequestServiceProvider = userRequestServiceProvider;
}
public String getCurrentUser() {
// Fetch request-scoped bean on demand
UserRequestService userRequestService = userRequestServiceProvider.getObject();
return "Current User Session ID: " + userRequestService.getUserId();
}
}
Conclusion
In this article, we have discussed in detail about what bean scopes are and how they are important for defining the lifetime of a bean object. In Spring Boot we have 6 types of bean scopes based on their features and use cases. It is important to remember that apart from Singleton and Prototype the remaining 4 beans scopes (request, session, application, websocket) are specific to web applications.
The following table summarizes each of these bean scopes –
Bean Scope | Created | Tied to / Equivalent Object | Destroyed after |
---|---|---|---|
Singleton | once per container | ApplicationContext | Container is destroyed |
Prototype | once each time requested | nothing | object is no longer required |
Request | once per HTTP request | HttpServletContext | Request is completed |
Session | once per user session | HttpSession | Session expired |
Application | once per Web application | ServletContext | Servlet Container is destroyed |
WebSocket | once per user web socket session | WebSocketSession | WS Session expires |