Friday, December 15, 2017

Spring Boot REST API - Spring Bean Scope illustration

We already know that Spring defines 5 types of bean scopes:

a) singleton
b) prototype
c) request
d) session
e) globalSession

Last three scopes are only available on web-aware application. Singleton and prototype are spring’s core scope and singleton is the default scope.




In this post, I am not going write a tutorial about spring bean scope. But we will develop a very simple Spring Boot REST API with 4 components (Spring bean) to observer behavior of each scope at run time. Each bean will have a different scope – singleton, prototype, request, and session. I do not cover globalsession in this example. We also have a REST controller which has injected references to all other components. Additionally, some components have references to other components so that we can get a clear picture of the scope of each bean. We already know that controller is a singleton bean itself.

Development Environment:

This application is tested with the following tools & technologies -
a) Eclipse Oxygen
b) JDK 1.8
c) Spring 1.5
d) Maven

Application Architecture: Please have a look at the image below which illustrates full application.


As you see, the controller has injected the reference to all other components. The component with scope ‘session’ has two references to singleton and prototype. And the application contains some other relationship between components as well. Annotation ‘autowired’ is used to satisfy dependency between beans.

 POM file: Minimum project configuration to run a simple Spring Boot REST application.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.monirthought.beanscope</groupId>
    <artifactId>bean-scope-test</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Controller Class: Controller has been injected 4 beans with different scopes. I name each bean along with scope so that we can easily visualize the scope of each bean. For an example, bean with ‘session’ scope has been identified as SessionBean.

@RestController
public class HelloController {

 
 public HelloController() {
  System.out.println("***************HelloController initialized******************");
 }
 
 @Autowired
 PrototypeBean prototypeBean;

 @Autowired
 SessionBean sessionBean;

 @Autowired
 RequestBean requestBean;

 @Autowired
 SingletonBean singletonBean;

 @RequestMapping("/")
 public String index() {
  sessionBean.printMessage();
  requestBean.printMessage();
  return "Greetings from Spring Boot!";
 }

}

Singleton Bean (SingletonBeanImpl):


package com.monirthought.beanscope.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("singleton")
public class SingletonBeanImpl implements SingletonBean {

 /* Prototype Bean autowired to observer prototype scope*/
 @Autowired
 PrototypeBean prototypeBean;

 private static int created = 0;

 /**
  * Each time this bean is initialized, 'created' increases by one.
  */
 public SingletonBeanImpl() {
  super();
  System.out.println("*************************** Singleton Bean Created " + (++created)
    + " Times ************************");
 }
}


@scope annotation is used to define this bean as a singleton. This singleton bean has a reference of a bean with prototype scope. We define a default constructor with a single print statement. Moreover, we define a static integer variable ‘created’. This ‘created’ value will be incremented by 1 each time this singleton bean is initialized. We can view this process on the console by the print statement in the constructor. I applied the similar operation to the all other beans.

Prototype Bean (PrototypeBeanImpl.java):


package com.monirthought.beanscope.beans;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * A spring bean with scope "prototpe"
 * 
 * @author Moniruzzaman Md
 *
 */
@Component
@Scope("prototype")
public class PrototypeBeanImpl implements PrototypeBean {

 private static int created = 0;

 /**
  * Each time this bean is initialized, 'created' increases by one.
  */
 public PrototypeBeanImpl() {
  System.out.println(
    "************************** Prototype Bean Created " + (++created) + " Times ************************");
 }

}

Session Scope (SessionBeanImpl.java): This scope only available web-aware application context.


package com.monirthought.beanscope.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;
import org.springframework.context.annotation.ScopedProxyMode;

/**
 * A spring bean with scope "session"
 * 
 * @author Moniruzzaman Md
 *
 */
@Repository
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionBeanImpl implements SessionBean {

 /* Singleton Bean autowired to observer session scope*/
 @Autowired
 SingletonBean singletonBean;
 
 /* Prototype Bean autowired to observer prototype scope*/
 @Autowired
 PrototypeBean prototypeBean;
 
 private static int created = 0;

 /**
  * Each time this bean is initialized, 'created' increases by one.
  */ 
 public SessionBeanImpl() {
  System.out.println("**************** Session Bean Created " + (++created) +" Times ****************");
 }

 public void printMessage() {
  System.out.println("********************** Session Bean - PrintMessage Method Called ******************");
 }
}

An additional proxyMode attribute is used to create a proxy which will be injected as a dependency by Spring. And also, Spring initiates the target bean when it is needed. Please note that there is no active request when web application context initialized.

Request Scope(RequestBeanImpl.java): Similar to the Session, this scope only available on web-aware application context. We also need proxyMode attribute.

package com.monirthought.beanscope.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBeanImpl implements RequestBean {

 private static int created = 0;

 /* Prototype Bean autowired to observer prototype scope*/
 @Autowired
 PrototypeBean prototypeBean; 
 
 /**
  * Each time this bean is initialized, 'created' increases by one.
  */
 public RequestBeanImpl() {
  System.out.println("*************************** Request Bean Created " + (++created)
    + " Times *****************************");
 }

 public void printMessage() {
  System.out.println("********************** Request Bean - PrintMessage Method Called ******************");
 }
}

Testing:

a) Download the full code from my GitHub repository.
b) Build the project with Maven: mvn clean package.
c) Run the application: mvn spring-boot:run

If you are able to run the application successfully, you can see the following log at the console:




As per log, we can see that -

a) Singleton bean created 1 time – As we have only one singleton class and it initialized during the application start.

b) Next prototype bean created – singleton bean has a reference to prototype bean, so during the initialize singleton bean, prototype bean also created. We know that prototype-scoped bean is created every time when it is requested.

c) Controller initialized – Controller is also a singleton bean by default. It is initialized by the container.

d) Prototype Bean created once more. As the controller has injected a reference of the prototype bean, it is initialized again. The controller also contains a reference of singleton bean, but it is not created again as this bean already initialized.

e) No request and session bean are created as there is no HTTP request or HTTP session at this moment.

f) Now open your web browser and hit: http://localhost:8080/ and observer the results -



1 session bean created, 1 request bean created, and prototype bean has been created 2 more times, total 4 times. Session bean and request has a reference of prototype bean. Prototype bean created each time when session and request bean initialized.

g) Hit the URL again: http://localhost:8080/ -



1 more request bean and 1 more prototype bean is created. No session bean is initialized as we are in the same session. In the other word, we have not started any new session.

h) Now we will initiate a new session. Close the browser, reopen and hit the URL again: http://localhost:8080/. Parallelly, open the URL with another browser.



So, we initiate two new sessions. 2 more new session beans initialized. Prototype bean is also created two times again. Additionally, 2 request beans are created.

Final note:

We have demonstrated different types of Spring bean scope in a simple Spring Boot REST API application. We use a common annotation like @Service, @Repository or @Component as like a real-world application so that we can examine what is happening behind-the-scenes.

Hope you enjoy this simple and easy example to understand the Spring bean scope.

If you have any question, idea, doubt or improvement, please send me a message at limon2009[at]gmail[dot]com

No comments:

Post a Comment