In this article, we’ll talk about Spring Bean Scopes, through detailed examples.
1. What are Spring Bean Scopes?
Spring Bean Scopes provide us with the ability to finely control the creation of bean instances. While we may choose to create certain bean instances as singletons or prototypes, we may want others to be created either per request or once per session.
To define the Scope of a bean, you have to use the @Scope
annotation, either on the type level or method level. The possible scopes are the following:
singleton
prototype
request
(can be used only with spring-boot-web)session
(can be used only with spring-boot-web)application
(can be used only with spring-boot-web)websocket
(can be used only with spring-boot-websocket)
In the following sections, we will take a deep look at how @Scope
annotation works and learn about all four possible scope options.
2. Setting Up the Project
First of all, you need to have Java installed, if you do not you can go through these tutorials depending on the OS your machine runs:
- Linux users: Install Java on Linux
- macOS users: Install Java on macOS
- Windows users: Install Java on Windows
For this tutorial, we are using the IntelliJ IDEA Community edition which you can download here.
If you are not using this IDE, you also need to download Maven here.
The next step is to visit Spring initializr and generate a project according to these settings shown in the image:
After having chosen the above options, press the GENERATE button, and a new spring boot project will be downloaded for you. Then you have to unzip the compressed archive and open the folder with your favorite IDE.
3. The @Scope Annotation
The @Scope
annotation can be either applied to types(@Component, @Service, etc. annotated classes) or methods(@Bean annotated methods). Moreover, it has the following attributes:
value
– The name of the scope for this bean, must be either empty or one of the possible scopes that we described in the first sectionscopeName
– an alias to the aboveproxyMode
– It defines if a component should be set as a scoped proxy, it defaults toScopedProxyMode.DEFAULT
and it can have the following values:ScopedProxyMode.DEFAULT
– Usually is NO, unless we have configured it otherwise at the component-scan levelScopedProxyMode.NO
– Usually used with Singleton scopeScopedProxyMode.INTERFACES
– Creates a JDK dynamic proxy that implements all interfaces exposed by the class of the target objectScopedProxyMode.TARGET_CLASS
– Creates a class-based proxy (CGLIB)
4. Singleton Scope
This is the default scope of Spring, and it means that when a Spring component is injected, the spring bean will only be created once.
Consider that we have the following example where we have a simple component and service as shown below :
import org.springframework.stereotype.Component; @Component public class Singleton {}
@Service public class ServiceForSingleton { private final Singleton singleton1; private final Singleton singleton2; public Service(Singleton singleton1, Singleton singleton2) { this.singleton1 = singleton1; this.singleton2 = singleton2; System.out.println(singleton1); System.out.println(singleton2); } }
Now if you do run the app, even though you have 2 singleton objects, you will see the following:
com.youlearncode.springBeanScopes.scopes.nonWeb.singleton.Singleton@1e7ab390 com.youlearncode.springBeanScopes.scopes.nonWeb.singleton.Singleton@1e7ab390
Which means that both point to the same object.
Last but not least, if for any reason you want to explicitly define the scope as a singleton, you can accomplish it like this:
@Scope("singleton")
OR@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
5. Prototype Scope
On the other hand, you can force the creation of a new Spring-managed bean each time it is injected, by using the prototype scope either like this: @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
or like this: @Scope("prototype")
For instance, if we have the following service and component:
import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Component public class Prototype {}
import org.springframework.stereotype.Service; @Service public class ServiceForPrototype { private final Prototype prototype1; private final Prototype prototype2; public ServiceForPrototype(Prototype prototype1, Prototype prototype2) { this.prototype1 = prototype1; this.prototype2 = prototype2; System.out.println(prototype1); System.out.println(prototype2); } }
and run the application, we will see two different hashcodes for the two objects, in contrast to what we saw with singleton scope:
com.youlearncode.springBeanScopes.scopes.nonWeb.prototype.Prototype@26cb5207 com.youlearncode.springBeanScopes.scopes.nonWeb.prototype.Prototype@15400fff
6. Web Scopes
In order to use the web scopes, you must have spring-boot-starter web as a dependency. In the following sections, we’ll see how each and every one of them works.
Note that when dealing with web scopes, we use the ScopedProxyMode.TARGET_CLASS
proxymode because the proxy makes sure that each thread gets its own instance of the bean by delegating each call to the actual bean instance that was created by the Spring container.
6.1 Request Scope
You can set the scope of a bean to be per request by using one the following three ways:
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
@RequestScope
– it’s exactly like the above
Now consider the following example with just a controller and a component:
import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE) public class HelloController { private final RequestScopeBean requestScopeBean; public HelloController(RequestScopeBean requestScopeBean) { this.requestScopeBean = requestScopeBean; } @GetMapping public String getAll() { System.out.println("RequestScopeBean: "+requestScopeBean); return "Hello"; } }
import org.springframework.stereotype.Service; import org.springframework.web.context.annotation.RequestScope; @Component @RequestScope public class RequestScopeBean { @PreDestroy public void preDestroy(){ System.out.println("Destroying bean: "+ this); } }
Note that we also added the @PreDestroy
annotation to print just before the destruction of the bean.
Going forward, if you want to understand how this works, you can start the application and go to http://localhost:8080/hello. Then , you can press refresh another two times and you will see the following logs:
RequestScopeBean: com.youlearncode.springBeanScopes.scopes.web.RequestScopeBean@3de2dd29 Destroying bean: com.youlearncode.springBeanScopes.scopes.web.RequestScopeBean@3de2dd29 RequestScopeBean: com.youlearncode.springBeanScopes.scopes.web.RequestScopeBean@b739854 Destroying bean: com.youlearncode.springBeanScopes.scopes.web.RequestScopeBean@b739854 RequestScopeBean: com.youlearncode.springBeanScopes.scopes.web.RequestScopeBean@5fcb5753
As you can infer, every time you sent a GET request, the previous bean was destroyed while a new one was created. Of course, if you remove the @RequestScope
annotation the bean will just be a singleton.
6.2 Session Scope
The logic with session scope is pretty similar to request scope but the life of a bean depends on the session. Just like with @RequestScope, you just have to add the annotation @SessionScope.
So let’s create a new bean and inject to the previous controller that we had created:
import jakarta.annotation.PreDestroy; import org.springframework.stereotype.Service; import org.springframework.web.context.annotation.SessionScope; @Service @SessionScope public class SessionScopeBean { @PreDestroy public void preDestroy(){ System.out.println("Destroying bean: "+ this); } }
and the controller will look like this:
import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE) public class HelloController { private RequestScopeBean requestScopeBean; private SessionScopeBean sessionScopeBean; public HelloController(RequestScopeBean requestScopeBean, SessionScopeBean sessionScopeBean) { this.requestScopeBean = requestScopeBean; this.sessionScopeBean = sessionScopeBean; } @GetMapping public String getAll() { System.out.println("RequestScopeBean: "+requestScopeBean); System.out.println("SessionScopeBean: "+sessionScopeBean); return "Hello"; } }
Now you can restart the application and press F5 many times; you will see the bean’s hashcode is the same. To trigger the creation of a new bean you can try to end the session by pressing F12 -> Application, then delete the cookie JSESSIONID
and if you press F5 again, you will see the hashcode will be different.
Last but not least, note that in this case the bean will not be destroyed, and this happens because the session-scoped beans are not destroyed by the Spring container, but by the servlet container.
6.3 Application Scope
As for the application scope, it is used to define that a bean will be created once per application context. Moreover, in order to set the scope of a bean as application, you should write the @ApplicationScope annotation at method or type level.
7. WebSocket Scope
The last scope that we will investigate is the websocket scope and in order to use it, you must have spring-boot-starter-websocket included.
As the name implies, the scope of a bean will be per websocket session, so for every websocket session we open, a new bean will be created.
To see that in action, we have to create a WebSocketConfig
class in order to enable websocket and register the endpoints:
import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocket @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/youlearncode") .setAllowedOriginPatterns("*") .withSockJS(); } }
Then we must create a bean as shown below:
@Component @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) public class PerWebSocketConnectionBean { @PreDestroy public void destroy(){ System.out.println("destroying bean: " + this); } }
and a controller in order to specify the endpoint of our message:
import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; @org.springframework.stereotype.Controller public class Controller { private final PerWebSocketConnectionBean perWebSocketConnectionBean; public Controller(PerWebSocketConnectionBean perWebSocketConnectionBean) { this.myBean = perWebSocketConnectionBean; } @MessageMapping("/youlearncode") @SendTo("/topic/messages") public String send(String message) throws Exception { System.out.println(perWebSocketConnectionBean); return message; } }
Finally, we have to create the websocket client as shown below:
<html> <head> <title>Websocket Scope Test</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script> </head> <body></body> <script> let socket = new SockJS('http://localhost:8080/youlearncode'); let stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { stompClient.subscribe('/topic/messages') stompClient.send("/app/youlearncode", {}, "hello"); }); window.addEventListener("beforeunload", function(e){ stompClient.disconnect(); }, false); </script> </html>
After having done everything, you can start the app, open the html file with your favorite browser and check the logs:
com.youlearncode.springBeanScopes.scopes.websocket.PerWebSocketConnectionBean@76fed1d3
If you do close the window, the client will disconnect, the bean will be destroyed and the logs will show:
destroying bean: com.youlearncode.springBeanScopes.scopes.websocket.PerWebSocketConnectionBean@76fed1d3
Of course, if you remove the websocket scope, the bean will not die per websocket session since the scope will be just singleton.
8. Conclusion
By now, you should know all the possible scopes that exist in Spring, when and why you should pick one, depending on the needs of your application. Finally, you can find the source code on our Github page.