Bean Scopes in Spring Framework: Comprehensive Guide

1. Overview

In this tutorial, we’ll delve into the different types of bean scopes within the Spring framework. The scope of a bean essentially defines its lifecycle and visibility within the contexts it operates.

The recent version of the Spring framework defines 6 distinct bean scopes in spring:

  • Singleton Scope
  • Prototype Scope
  • Web-Aware Scopes
    • Request Scope
    • session Scope
    • Application Scope
    • Websocket Scope

2. Singleton Scope

When we define a bean with the singleton scope, the container creates only one instance of that bean. Every time we call that bean, it gives back the same object, which is stored in the cache.

Any modifications we make to the object will affect all the references of the bean. Singleton scope is the default scope, if no other scope is explicitly specified.

Let’s start by creating a Student class to illustrate the idea of scopes:

public class Student {
    private String rollNumber;

    // standard constructor, getters and setters
}

Next, we create the bean with the singleton scope using the @Scope annotation:

@Bean
@Scope("singleton")
public Student singleStudent() {
    return new Student();
}

We can also use a constant instead of the String value like this:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

Now, let’s write a test that demonstrates that two objects referring to the same bean will have the same values, even if only one of them changes their state, as they are both pointing to the same bean instance:

private static final String ROLL_NUMBER = "CS101";

@Test
public void whenSingletonScope_thenEqualRollNumbers() {
    ApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("scopes.xml");

    Student singleStudentA = (Student) applicationContext.getBean("singleStudent");
    Student singleStudentB = (Student) applicationContext.getBean("singleStudent");

    singleStudentA.setRollNumber(ROLL_NUMBER);
    Assert.assertEquals(ROLL_NUMBER, singleStudentB.getRollNumber());

    ((AbstractApplicationContext) applicationContext).close();
}

In this case, we expect that the scopes.xml file will contain the XML representations of the beans being used:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="singleStudent" class="com.devstaq.scopes.Student" scope="singleton"/>    
</beans>

3. Prototype Scope

A bean with the prototype scope will provide a different instance each time we request it from the container. We define this by assigning the value prototype to the @Scope annotation in the bean definition:

@Bean
@Scope("prototype")
public Student multipleStudent() {
    return new Student();
}

As mentioned earlier, spring allows us to use constant like we did for the singleton scope:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Now, we’ll write a test similar to the previous one, where two objects request the same bean name with the prototype scope. They will have different states since they’re not pointing to the same bean instance anymore:

private static final String ROLL_NUMBER = "CS101";
private static final String ROLL_NUMBER_OTHER = "EC102";

@Test
public void whenPrototypeScope_thenDifferentRollNumbers() {
    ApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("scopes.xml");

    Student multipleStudentA = (Student) applicationContext.getBean("multipleStudent");
    Student multipleStudentB = (Student) applicationContext.getBean("multipleStudent");

    multipleStudentA.setRollNumber(ROLL_NUMBER);
    multipleStudentB.setRollNumber(ROLL_NUMBER_OTHER);

    Assert.assertEquals(ROLL_NUMBER, multipleStudentA.getRollNumber());
    Assert.assertEquals(ROLL_NUMBER_OTHER, multipleStudentB.getRollNumber());

    ((AbstractApplicationContext) applicationContext).close();
}

The scopes.xml file is similar to the one we saw in the previous section, but it also includes the XML definition for the bean with the prototype scope:

<bean id="multipleStudent" class="com.devstaq.scopes.Student" scope="prototype"/>

4. Web-Aware Bean Scopes in Spring

As we mentioned earlier, there are four extra scopes that are only available in a web-aware application context. We don’t use these very often.

The request scope creates a bean instance for a single HTTP request, whereas the session scope generates a bean instance for an HTTP Session.

The application scope and the websocket scope create the bean instance for the lifecycle of a ServletContext and a particular WebSocket session, respectively.

Let’s create a class named GreetingGenerator for bean instantiation:

public class GreetingGenerator {
    private String message;
    
    // standard getter and setter
}

4.1. Request Scope

We can use the @Scope annotation to define the bean with the request scope:

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public GreetingGenerator requestScopedBean() {
    return new GreetingGenerator();
}

The proxyMode attribute is necessary because there is no active request at the moment of the web application context instantiation. Spring generates a proxy to inject as a dependency and instantiates the target bean when it’s required in a request.

We can also use a @RequestScope composed annotation as a shortcut for the above definition:

@Bean
@RequestScope
public GreetingGenerator requestScopedBean() {
    return new GreetingGenerator();
}

Next, we can define a controller that has an injected reference to the requestScopedBean. We need to access the same request twice to test the web-specific scopes.

If we show the message every time the request runs, we notice that the value resets to null, even though it’s updated later in the method. This happens because each request gets a different bean instance:

@Controller
public class ClientController {
    @Resource(name = "requestScopedBean")
    GreetingGenerator requestScopedBean;

    @RequestMapping("/client/request")
    public String getRequestScopeMessage(final Model model) {
        model.addAttribute("previousMessage", requestScopedBean.getMessage());
        requestScopedBean.setMessage("Hello, World!");
        model.addAttribute("currentMessage", requestScopedBean.getMessage());
        return "scopesExample";
    }
}

4.2. Session Scope

Just like we define other beans, we can also define a bean with session scope:

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public GreetingGenerator sessionScopedBean() {
    return new GreetingGenerator();
}

To simplify the bean definition, we can use a dedicated composed annotation:

@Bean
@SessionScope
public GreetingGenerator sessionScopedBean() {
    return new GreetingGenerator();
}

Next, we create a controller that references the sessionScopedBean. To demonstrate that the value of the message field remains the same for the session, we need to run two requests.

In this scenario, when the request is made for first time, the value of message is null. However, once it’s modified, that value remains for subsequent requests because the bean instance remains the same throughout the session.

@Controller
public class ClientController {
    @Resource(name = "sessionScopedBean")
    GreetingGenerator sessionScopedBean;

    @RequestMapping("/client/session")
    public String getSessionScopeMessage(final Model model) {
        model.addAttribute("previousMessage", sessionScopedBean.getMessage());
        sessionScopedBean.setMessage("Hello, Earth!");
        model.addAttribute("currentMessage", sessionScopedBean.getMessage());
        return "scopesExample";
    }
}

4.3. Application Scope

The 9 creates a bean instance for the lifecycle of a ServletContext. This is similar to the singleton scope, but there’s a crucial difference in the scope of the bean.

Application scoped beans share the same instance across multiple servlet-based applications running in the same ServletContext. On the other hand, singleton scoped beans are scoped to a single application context only.

Let’s define a bean with the application scope:

@Bean
@Scope(
  value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public GreetingGenerator applicationScopedBean() {
    return new GreetingGenerator();
}

Just like the request and session scopes, we can use a shorter version:

@Bean
@ApplicationScope
public GreetingGenerator applicationScopedBean() {
    return new GreetingGenerator();
}

Now, let’s create a controller that references this bean:

@Controller
public class ClientController {
    @Resource(name = "applicationScopedBean")
    GreetingGenerator applicationScopedBean;

    @RequestMapping("/client/application")
    public String getApplicationScopeMessage(final Model model) {
        model.addAttribute("previousMessage", applicationScopedBean.getMessage());
        applicationScopedBean.setMessage("Hello, Mars!");
        model.addAttribute("currentMessage", applicationScopedBean.getMessage());
        return "scopesExample";
    }
}

In this case, once we set the value of message in the applicationScopedBean, it stays the same for all future requests, sessions, and even for other servlet applications accessing this bean, as long as they run in the same ServletContext.

4.4. WebSocket Scope

Finally, let’s define a bean with the WebSocket scope:

@Bean
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public GreetingGenerator websocketScopedBean() {
    return new GreetingGenerator();
}

When we first access WebSocket scoped beans, they get stored in the WebSocket session attributes. Every time we access that bean throughout the entire WebSocket session, we get the same instance.

So, it works like a singleton, but this applies only within a WebSocket session.

5. Conclusion

In this article, we explored the different bean scopes provided by Spring and their intended uses. We learned how to define beans with different scopes and how to reference them in our controllers. We also understood the differences between these scopes and when to use each one.

Understanding these scopes and using them appropriately in our applications can help us manage our resources more efficiently and build more robust applications.

Scroll to Top