Spring Bean Scopes
1 CommentLast Updated on May 18, 2020 by jt
When you start a Spring application, the Spring Framework creates beans for you. These Spring beans can be application beans that you have defined or beans that are part of the framework. When the Spring Framework creates a bean, it associates a scope with the bean. A scope defines the runtime context within which the bean instance is available. In Spring, a bean can be associated with the following scopes:
- Singleton
- Prototype
- Request
- Session
- Global session
- Application
Note: Out of the preceding scopes, Request, Session, and Application are for beans in Web-aware applications. Global session beans are for portlets.
In this post, I will discuss the different bean scopes with use cases to illustrate when to use which scope.
The Singleton Bean Scope
When you create a bean with the Singleton scope, the Spring Framework will create an instance of the bean only once. The Framework returns that instance each time the bean is requested by your application code.
I will explain through a bare minimal Spring application that models online blogs and authors.
The code of the Blog
class is this.
Blog.java
package guru.springframework.beanscope.domain; import org.springframework.beans.factory.annotation.Autowired; public class Blog { private int id; private String title; public Blog() { } public Blog(int id, String title) { this.id = id; this.title = title; } /*Getter and setter for fields*/ public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
As you can see, there is nothing special about the Blog
class. We have a POJO with two fields – id
and title
with corresponding getter and setter methods. The class also has a default constructor and an overloaded one to initialize a Blog
object.
Next, I will start by creating a Blog bean with the Singleton scope.
The code for the configuration class is this.
SingleDemo.java
package guru.springframework.beanscope.config; import guru.springframework.beanscope.domain.Blog; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class SingletonDemo { @Bean(name = "blog") @Scope("singleton") public Blog getBlog() { return new Blog(); } }
This configuration class has a getBlog()
method that returns a Blog
bean. This method, in addition to the @Bean
annotation, is annotated with @Scope("singleton")
to set the scope of the bean.
Note: Spring Beans by default, are Singleton. This means, if you do not explicitly define a scope, the Spring Framework will create the bean with the Singleton scope. Therefore, the @Scope
annotation in the preceding example is redundant.
Now that we have written the code to create a Singleton Blog bean, let’s test the code.
I am using JUnit with Spring Boot Test to unit test the code.
The test code is this.
SingletonDemoTest.java
@RunWith(SpringRunner.class) @SpringBootTest class SingletonDemoTest { @Autowired private ApplicationContext applicationContext; @Test void getBlog() { Blog blogInstance1 = applicationContext.getBean("blog1", Blog.class); Blog blogInstance2 = applicationContext.getBean("blog1", Blog.class); assertThat(blogInstance1.equals(to(blogInstance2))); } }
In the preceding test, I first autowired in the application context. In the test case, I used the application context to lookup the Blog
bean and obtained two instances of the bean. I then performed an AssertJ to assert that both the instances are equal.
When I run the test, the test passes as expected because we defined Blog
as a Singleton bean. Therefore the Spring Framework returns the same bean instance for both the lookups made to Blog
.
The Prototype Bean Scope
When you create a bean with the Prototype scope, the Spring Framework will create a bean instance each time the bean is requested by your application code.
Going with the same example, I’ll write a configuration that creates a Blog bean with the prototype scope.
The code of the configuration class is this.
PrototypeDemo.java
package guru.springframework.beanscope.config; import guru.springframework.beanscope.domain.Blog; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class PrototypeDemo { @Bean(name = "blog2") @Scope("prototype") public Blog getBlog() { return new Blog(); } }
The test code is this.
PrototypeDemoTest.java
package guru.springframework.beanscope.config; import guru.springframework.beanscope.domain.Blog; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.internal.bytebuddy.implementation.MethodDelegation.to; import static org.junit.jupiter.api.Assertions.*; @RunWith(SpringRunner.class) @SpringBootTest class PrototypeDemoTest { @Autowired private ApplicationContext applicationContext; @Test void getBlog() { Blog blogInstance1 = applicationContext.getBean("blog2", Blog.class); Blog blogInstance2 = applicationContext.getBean("blog2", Blog.class); assertThat(blogInstance1).isNotEqualTo(blogInstance2); } }
The preceding test performs two lookups of the Blog
bean with the name blog2
. Because we declared the scope of the blog2
bean as Prototype, Spring returns a new instance of Blog
for each lookup. As a result, isNotEqualTo
assertion passes.
Singleton with Injected Prototype Bean Scope
In Enterprise Spring applications, you will typically have beans injected with other beans. So, it is important to understand what happens when a bean is injected with a bean with different scope.
For example, what happens when a Singleton bean is injected with a Prototype bean?
Let us find out with an example. I’ll extend the existing Blog example to introduce an Author
domain object with the relationship, blog has-a author.
This is the Author
POJO.
Author.java
package guru.springframework.beanscope.domain; public class Author { private int id; private String name; public Author() { } public Author(int id, String name) { this.id = id; this.name = name; } /*Getter and setter methods*/ public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
The refactored Blog
POJO is this.
Blog.java
package guru.springframework.beanscope.config; import guru.springframework.beanscope.domain.Author; import guru.springframework.beanscope.domain.Blog; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class SingletonInjectedPrototypeDemo { @Bean(name = "blog3") @Scope("singleton") public Blog getBlog() { return new Blog(); } @Bean(name = "author1") @Scope("prototype") public Author getAuthor() { return new Author(); } }
The next step is to write a configuration.
The code of the configuration class is this.
SingletonInjectedPrototypeDemo.java
package guru.springframework.beanscope.config; import guru.springframework.beanscope.domain.Author; import guru.springframework.beanscope.domain.Blog; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; @Configuration public class SingletonInjectedPrototypeDemo { @Bean(name = "blog3") @Scope("singleton") public Blog getBlog() { return new Blog(); } @Bean(name = "author1") @Scope("prototype") public Author getAuthor() { return new Author(); } }
This configuration creates a Singleton Blog
and Prototype Author
beans.
The test class for the preceding configuration is this.
SingletonInjectedPrototypeDemoTest .java
package guru.springframework.beanscope.config; import guru.springframework.beanscope.domain.Author; import guru.springframework.beanscope.domain.Blog; import org.junit.jupiter.api.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.internal.bytebuddy.implementation.MethodDelegation.to; import static org.junit.jupiter.api.Assertions.*; @RunWith(SpringRunner.class) @SpringBootTest class SingletonInjectedPrototypeDemoTest { @Autowired private ApplicationContext applicationContext; @Test void getBlog() { Blog blogInstance1 = applicationContext.getBean("blog3", Blog.class); Blog blogInstance2 = applicationContext.getBean("blog3", Blog.class); assertThat(blogInstance1.equals(to(blogInstance2))); Author authorInstance1 = blogInstance1.getAuthor(); Author authorInstance2 = blogInstance2.getAuthor(); assertThat(authorInstance1.equals(to(authorInstance2))); } }
On execution, this test case passes.
You might be expecting the second assertion to fail. Because Author
being a prototype, Spring should have created two different bean instances.
So why does the equality assertion passes?
This is known as the scoped bean injection problem. In Spring, dependencies are resolved at instantiation time. This means when you autowire the Author prototype-scoped bean into the Blog singleton-scoped bean, Spring will:
- Instantiate a brand new
Author
prototype bean. - Inject the
Author
instance into the singletonBlog
bean. That exact sameAuthor
instance will be the sole instance that is ever supplied to theBlog
bean
The Request Bean Scope
The request scope is applicable to beans of Web aware applications. This scope defines a single bean definition that lives within a single HTTP request. This means every HTTP request will have its own instance of a bean.
I’ll demonstrate how the Request scope works through a REST controller.
The code of the REST controller is this.
RequestScopeController.java
package guru.springframework.beanscope.controllers; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope; @RestController @RequestScope public class RequestScopeController { @GetMapping("message") public String getMessage() { System.out.println(this); return "Hello from RequestScopeController"; } }
The preceding code creates a REST controller. The @RequestScope
annotation sets the scope of the controller to Request. The getMessage()
handler method print out the current controller instance and returns a String
as the response.
Note: The @RequestScope
annotation is equivalent to @Scope("singleton")
Run the application. Then open a browser to make two requests with this URL:
http://localhost:8080/message
The output in the IntelliJ Console is this.
In the preceding output, note that two different controller instances served the two requests you made.
In real-world programming, you won’t create controllers with request scope. Controllers should always be Singleton. It was only for the purpose of demonstration, I showed one. A typical use case to create a bean in request scope is for information that should only be valid on one page. For example, the confirmation of an order. The bean will be valid until the page is reloaded.
The Session Bean Scope
The session scope defines a single bean definition which lives within the lifecycle of an HTTP Session. Similar to the Request scope, the Session scope is applicable to beans in Web applications.
The code for a REST controller with session scope is this.
package guru.springframework.beanscope.controllers; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope; import org.springframework.web.context.annotation.SessionScope; import org.w3c.dom.ls.LSOutput; @RestController @SessionScope public class SessionScopeController { @GetMapping("/session/message") public String getMessage() { System.out.println(this); return "Hello from SessionScopeController"; } }
Run the application and access the controller multiple times from a browser window using this URL.
http://localhost:8080/session/message
The application output is this.
Note the output. As the requests are coming from the same session, the same controller instance is serving the requests.
Open a browser window in incognito mode and access the same URL a few times.
The application output now is this.
As you can see, because the requests are now being sent from a different session, a new controller instance serves the current set of requests.
Other Scopes
There are two other lesser used scopes: Global-Session and Application scopes
Global session scope defines a single bean definition to the lifecycle of a global HTTP Session. This scope is valid when used in a portlet context.
When your application is built of portlets, they run in Portlet container. Each portlet has its own session, but if you want to store variables global for all portlets in your application than you should scope them in Global Session.
In the application scope, Spring creates a bean instance per web application runtime. It is similar to singleton scope, with one major difference. Singleton scoped bean is singleton per ApplicationContext where application scoped bean is singleton per ServletContext. Please note that there can be multiple application contexts for a single application.
Summary
When you develop Enterprise Applications using the Spring Framework, selecting the right scope for your beans is crucial. Most of the time, the vast majority of business logic we create can be safely kept in stateless objects. And the best choice for stateless beans is the singleton scope. This is the reason for Singleton being the default scope.
The prototype scope is better for stateful beans to avoid multithreading issues.
A session-scoped bean is useful to hold authentication information getting invalidated when the session is closed (by timeout or logout). You can store other user information that you don’t want to reload with every request here as well.
The source code for this post can be found here on GitHub.
One comment