Description: https://images.manning.com/360/480/resize/book/b/fa6c98f-fdb2-465b-9cb8-4b9463e0dfaa/Spilca-SpringQ-MEAP-HI.png

From Spring Start Here by Laurențiu Spilcă

This article covers

  • Using the Spring web scopes
  • Implementing a simple login functionality for a web app
  • Redirecting from one page to another in a web app


Take 40% off Spring Start Here by entering fccspilca2 into the discount code box at checkout at manning.com.


Spring manages a bean’s lifecycle differently depending on how you declare the bean in the Spring context. Spring has custom ways to manage instances for web apps by using the HTTP request as a point of reference. Spring is pretty cool, isn’t it?

In any Spring app, you can choose to declare a bean as

  • Singleton – The default bean scope in Spring, for which the framework uniquely identifies each instance with a name in the context.
  • Prototype – The bean scope in Spring, for which the framework only manages the type and creates a new instance of that class every time someone requests it (directly from the context or through wiring or autowiring).

In this article, you’ll learn that in web apps you can use other bean scopes. These bean scopes are only relevant to web applications. We call them web scopes, and they’re as follows:

  • Request scope – Spring creates an instance of the bean class for every HTTP request. The instance exists only for that specific HTTP request.
  • Session scope – Spring creates an instance and keeps the instance in the server’s memory for the full HTTP session. Spring links the instance in the context with the client’s session.
  • Application scope – The instance is unique in the app’s context, and it’s available as the app is running.

To teach you how these web scopes work in a Spring application, we’ll work on an example throughout the chapter. In this example, we implement a login functionality. Most of the web apps today offer their users the possibility to log in and access an account, and the example is also relevant from a real-world perspective.

First, we’ll use a request-scoped bean to take the user’s credentials for login and make sure the app uses them only for the login request. Then, we’ll use a session-scoped bean to store all the relevant details we need to keep for the logged-in user as long as the user remains logged in. Finally, we’ll use the application-scoped bean to add to our app a capability to count logins. Figure 1 shows you the steps we take to implement this app throughout the chapter.


Figure 1. We’ll implement the login functionality in three steps. For each step we implement, we’ll need to use a different bean scope. First, we’ll use a request-scoped bean to implement the login logic without risking storing the credentials for longer than the login request. We then decide what details we need to store for the authenticated user in a session-scoped bean. Finally, we implement a feature to count all the login requests, and we use an application-scoped bean to keep the number of login requests.


Using the request scope in a Spring web app

In this section, you learn how to use request-scoped beans in Spring web apps. Web apps are focused on HTTP requests and responses. For this reason, and often in web apps, certain functionalities are easier to manage if Spring offers you a way to manage the bean lifecycle in relationship with the HTTP request.

A request-scoped bean is an object managed by Spring, for which the framework creates a new instance for every HTTP request. The app can use the instance only for the request that created it. Any new HTTP request (from the same or other clients) creates and uses a different instance of the same class (figure 2).


Figure 2. For every HTTP request, Spring provides a new instance for the request-scoped bean. When using a request scoped bean, you can be sure the data you add on the bean is only available on the HTTP request that created the bean. Spring manages the bean type (the plant) and uses it to get instances (coffee beans) for each new request.


Key aspects of request-scoped beans

Before diving into implementing a Spring app that uses request-scoped beans, I’d like to shortly enumerate here the key aspects of using this bean scope. These aspects help you analyze whether a request-scoped bean is the right approach to use in your case in a real-world scenario. Keep in mind these relevant aspects of request-scoped beans:

Fact

Consequence

To consider

To avoid

Spring creates a new instance for every HTTP request from any client

Spring creates a lot of instances of this bean in the app’s memory during its execution.

The number of instances isn’t a big problem usually. This is because these instances are short-lived. The app doesn’t need them for more than the time the HTTP request needs to complete. Once the HTTP request completes, the app releases the instances, and they are garbage-collected.

Make sure you don’t implement a time-consuming logic Spring needs to execute to create the instance (like getting data from a database or implementing a network call). Avoid writing logic in the constructor or a @PostConstruct method for request-scoped beans.

Only one request can use an instance of a request-scoped bean.

Instances of request-scoped beans aren’t prone to multithread-related issues as only one thread (the one of the request) can access them.

You can use the instance’s attributes to store data used by request.

Don’t use synchronization techniques for the attributes of these beans. These techniques are redundant, and they only affect the performance of your app.

Now let’s demonstrate the use of a request-scoped bean in an example. We’ll implement a web application’s login functionality, and we’ll use a request-scoped bean to manage the user’s credentials for the login logic.

NOTE  A login example, such as this one, we use for our demonstration in this article is excellent for didactic purposes, but in a production-ready app, it’s better to avoid implementing yourself authentication and authorization mechanisms. In a real-world Spring app, we use Spring Security to implement anything related to authentication and authorization. Using Spring Security (which is also part of the Spring ecosystem) simplifies your implementations and ensures you don’t (by mistake) introduce vulnerabilities when writing the application-level security logic. I recommend you also read Spring Security in Action, which is a book I authored and which describes in detail how to use Spring Security to protect your Spring app.

To make things straightforward, we consider a set of credentials which we bake into our application. In a real-world app, the app stores the users in a database. It also encrypts the passwords to protect them. For now, we only focus on the purpose of this article – discussing the Spring web bean scopes.

Let’s create a Spring Boot project and add the needed dependencies. You find this example in project sq-ch9-ex1 of the projects provided. You can add the dependencies directly when creating the project (for example, using start.spring.io) or afterward in your pom.xml. For this example, we use the web dependency and Thymeleaf as a templating engine. The next code snippet shows the dependencies you need to have in your pom.xml file.

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

We’ll create a page that contains a login form asking for a user’s name and password. The app compares the username and the password with a set of credentials it knows (in my case, user “natalie” with password “password”). If we provide correct credentials (they match with the credentials known by the app), then the page displays a message “You are now logged in” under the login form. If the credentials we provide are incorrect, then the app displays a message: “Login failed”.

We need to implement a page (representing our view) and a controller class. The controller sends to the view the message it needs to display according to the login’s result (figure 3).


Figure 3. We need to implement the controller and the view. In the controller, we implement an action that finds out if the credentials sent in the login request are valid. The controller sends a message to the view, and the view displays this message.


Listing 1 shows the HTML login page that defines the view in our app. You need to store the page in the resources/templates folder of your Spring Boot project. In this example, let’s name the page login.html. To display the message with the logic’s result, we need to send a parameter from the controller to the view. I named this parameter “message”, as you can observe in listing 1, where I used the syntax ${message} to display this in a paragraph under the login form.

Listing 1. The definition of the login page login.html

 
 <!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org">    #A
 <head>
   <meta charset="UTF-8">
   <title>Login</title>
 </head>
 <body>
   <form action="/" method="post">    #B
      Username: <input type="text" name="username" /><br />    #C
      Password: <input type="password" name="password" /><br />     #C
      <button type="submit">Log in</button>     #D
   </form>
  
   <p th:text="${message}"></p>      #E
 </body>
 </html>
  

#A We define the “th” Thymeleaf prefix to use the templating engine’s capabilities.

#B We define an HTML form to send the credentials to the server.

#C The input fields are used to write the credentials, username and password.

#D When the user clicks the submit button, the client makes an HTTP POST request with the credentials.

#E We display a message with the result of the login request under the HTML form.

A controller action needs to get the HTTP request (from the dispatcher servlet). Let’s define the controller and the action that receives the HTTP request for the page we created in listing 1.  In listing 2, you find the definition of the controller class. We map the controller’s action to the web app’s root path (“/”). I name the controller LoginController.

Listing 2. The controller’s action is mapped to the root path

 
 @Controller    #A
 public class LoginController {
  
  
   @GetMapping("/")      #B
   public String loginGet() {
     return "login.html";    #C
   }
 }
  

#A We use the @Controller stereotype annotation to define the class as a Spring MVC controller.

#B We map the controller’s action to the root (“/”) path of the application

#C We return the view name we want to be rendered by the app.

Now that we have a login page, we want to implement the login logic. When a user clicks on the submit button, we want the page to display a proper message under the login form. If the user submitted the correct set of credentials, the message is “You are now logged in”, otherwise, the displayed message is “Login failed” (figure 4).


Figure 4. The functionality we implement in this section. The page displays a login form for the user. Then the user provides valid credentials, the app displays a message telling them they successfully logged in. If the user provides incorrect credentials, the app tells the user that the login failed.


To process the HTTP POST request that the HTML form creates when the user clicks on the submit button, we need to add one more action to our LoginController. This action takes the client’s request parameters (the username and the password) and sends a message to the view according to the login result. Listing 3 shows you the definition of the controller’s action, which we’ll map to the HTTP POST login request.

Mind that we didn’t implement the login logic yet. In listing 3, we take the request and send a message in response according to a variable representing the request’s result, but this variable (in listing 3 named loggedIn) is always “false”. In the next listings in this section, we complete this action by adding a call to the login logic. This login logic returns the login result based on the credentials the client sent in the request.

Listing 3. The controller’s login action

 
 @Controller
 public class LoginController {
  
   @GetMapping("/")
   public String loginGet() {
     return "login.html";
   }
  
   @PostMapping("/")    #A
   public String loginPost(
       @RequestParam String username,    #B
       @RequestParam String password,    #B
       Model model      #C
   ) {
     boolean loggedIn = false;     #D
  
     if (loggedIn) {     #E
       model.addAttribute("message", "You are now logged in.");    #E
     } else {    #E
       model.addAttribute("message", "Login failed!");    #E
     }    #E
  
     return "login.html";    #F
   }
 }
  

#A We are mapping the controller’s action to the HTTP POST request of the login page.

#B We get the credentials from the HTTP request parameters.

#C We declare a Model parameter to send the message value to the view.

#D When we later implement the login logic, this variable stores the login request result.

#E Depending on the result of the login, we send a specific message to the view.

#F We return the view name, which is still login.html, and we remain on the same page.

Figure 5 visually describes the link between the controller class and the view we implemented.


Figure 5. The dispatcher servlet calls the controller’s action when someone submits the HTML login form. The controller’s action gets the credentials from the HTTP request parameters. According to the login result, the controller sends a message to the view, and the view displays this message under the HTML form.


Ok! We have a controller and a view, but where the request scope in all of this is? The only class we wrote is the LoginController, and we left it a singleton, which is the default Spring scope. We don’t need to change the scope for LoginController as long as it doesn’t store any detail in its attributes, but remember, we need to implement the login logic. The login logic depends on the user’s credentials, and we have to take into consideration two things about these credentials:

  1. The credentials are sensitive details, and you don’t want to store them in the app’s memory for longer than the login request.
  2. More users with different credentials might attempt to log in simultaneously.

Considering these two points, we need to make sure if we use a bean for implementing the login logic, each instance needs to be unique for each HTTP request. We need to use a request-scoped bean. We’ll extend the app as presented in figure 5. We add a request-scoped bean LoginProcessor. This bean takes the credentials on the request and validates them (figure 6).


Figure 6. The LoginProcessor bean is request-scoped. Spring makes sure to create a new instance for each HTTP request. The bean implements the login logic. The controller calls a method it implements. The method returns true if the credentials are valid and false otherwise. Based on the value the LoginProcessor returns, the LoginController sends the right message to the view.


Listing 4. shows the implementation of the LoginProcessor class. To change the scope of the bean, we use the @RequestScoped annotation. We still need to make the class a bean in the Spring context by either using the @Bean annotation in a configuration class or a stereotype annotation. In this example, I chose to annotate the class with the @Component stereotype annotation.

Listing 4/ The request-scoped LoginProcessor bean implements the login logic

 
 @Component    #A
 @RequestScope    #B
 public class LoginProcessor {
  
   private String username;    #C
   private String password;    #C
  
   public boolean login() {     #D
     String username = this.getUsername();
     String password = this.getPassword();
  
     if ("natalie".equals(username) && "password".equals(password)) {
       return true;
     } else {
       return false;
     }
   }
  
   // omitted getters and setters
 }
  

#A We annotate the class with a stereotype annotation to tell Spring this is a bean.

#B We use the @RequestScope annotate to change the bean’s scope to request scope. This way, Spring creates a new instance of the class for every HTTP request.

#C The bean stores the credentials as attributes.

#D The bean defines a method for implementing the login logic.

You can run the application and access the login page using the localhost:8080 address in your browser’s address bar. Figure 7 shows you the app’s behavior after accessing the page and for using valid and incorrect credentials.


Figure 7. When accessing the page in a browser, the app shows a login form. You can use valid credentials, and the app displays a successful login message. If you use incorrect credentials, the app displays a “Login failed!” message.


That’s all for now. Stay tuned for part 2, but in the meantime, check out the book on Manning’s liveBook platform here.