Spring MVC

Last Updated on: January 19, 2021 pm

Spring MVC Overview

  • Framework for building web application in Java.
  • Based on Model-View-Controller design pattern.
  • Leverages features of the Core Spring Framework (IoC, DI).

MVC

Components

  • A set of web pages to layout UI Components
  • A collection of Spring beans
  • Spring Configuration (XML, Annotations or Java)

Front Controller

  • Known as DispatchServlet
  • Part of the Spring Framework
  • Already developed by Spring Dev Team

You need to create:

  • Model Objects
  • View Templates
  • Controller Classes

Controller

  • Contains your business logic

    • Handle the request
    • Store/Retrieve data(db, web service)
    • Place data in model
  • Send to appropriate view template

Model

  • Contains your data

  • Store/Retrieve data via backend systems

    • Database, web service, etc…
    • Use a Spring bean if you like
  • Place your data in a model

    • Data can be Java Object or colletion.

Like your luggage.

View Template

Most common is JSP and JSTL.

Developer creates a page.

Display data.

Spring MVC Configuration Process

Preparation

  1. Create a Java EE Application in IntelliJ IDEA.

Create a Java EE Application

  1. Copy the two XML files to WEB-INF directory.

    Copy XMLs

  2. Open Project Structure

Project Structure
  1. Add Spring Module to the project.

Add Spring Module

  1. Download Spring Library to the project.
Download Spring Library
  1. Do the same thing to the Spring MVC library. ** Click the **Apply and OK.

Download Spring MVC

  1. Open the Project Structure again. Fix the problems in the Problems section.

Fix the Problems

  1. Move the lib directory to the WEB-INF. Add two JSTL.jar to lib as well.

Move lib

  1. Open Project Structure again and modify the version path of the two dependencies to ./web/WEB-INF/lib.

Change path

  1. Create view directory.

Create View dir to store the pages

Part 1

Add configuration to file: WEB-INF/web.xml

  1. Configure Spring MVC Dispatch Servlet

    Configure Dispatch Servlet

  2. Set up URL mappings to Spring MVC Dispatcher Servlet

    For every URL coming, pass it off to the DispatchServlet.

    The Servlet-name here should match the servlet-name in the above Servlet.

Part 2

Add configuration to servlet.xml.

  1. Add support for Spring component scanning.

Add Component Scan

  1. Add support for conversion, formatting and validation.

Spring can perform conversion, and also formatting data and perform validation by adding this support.

  1. Configure Spring MVC View Resolver.

How do we display those pages? / Where are the pages located?

Prefix: 前缀

Suffix: 后缀

Example:

Prefix and Suffix

Create Controllers and Views

Create Controller class

1
2
3
4
@Controller
public class HomeController {

}

Annotate class with @Controller.

@Controller inherits from @Component and it supports scanning.

Define Controller Methods

1
2
3
public String showPage(){

}

Add Request Mapping to Controller method

1
2
3
4
@RequestMapping("/")
public String showPage(){

}

RequestMapping() annotation maps a path to this given method, so you can define any method name.

Here, this request mapping will handle all kinds of requests, including get post and so on.

Return View Name

1
2
3
4
@RequestMapping("/")
public String showPage(){
return "main-menu";
}

Spring will find teh view page by appending the prefix and suffix.

WEB-INF/view/main-menu.jsp

Develop View Page

main-menu.jsp

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Spring MVC Demo - Page</title>
</head>
<body>
<h2> Test</h2>
<p> Hello World</p>
</body>
</html>

Read Form Data from Spring MVC

Application Flow

The browser goes to the website /showForm.

This goes to the HelloWorld Controller.

The main purpose of the controller is to show helloworld-form.jsp

Key Point: use one HelloWorld Controller to process two RequestMappings.

Development Process

  1. Create Controller Class
1
2
3
4
5
6
7
8
@Controller
public class HomeController {

@RequestMapping("/")
public String showPage(){
return "main-menu";
}
}
  1. Show HTML form.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class HelloWorldController {

@RequestMapping("/showForm")
public String showForm(){
return "helloworld-form";
}

@RequestMapping("/processForm")
public String processForm(){
return "helloworld";
}
}
  1. Process HTML form.
1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<title>Hello World - Input Form</title>
</head>
<body>
<form action="processForm" method="GET">
<input type="text" name="studentName" placeholder="What's your name?"/>
<input type="submit"/>
</form>
</body>
</html>

Add Data to Spring Model

Passing model to your controller

Parameters in the Controller method:

  • HttpServletRequest - Holds HTML form data

  • Model - Container for your form data

  • HttpServletResponse

View Template - JSP

Add attribute name & attribute value.

model.addAttribute("message", result)

Example

1.Build your controller method

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/processFormVersionTwo")
public String letsShoutDude(HttpServletRequest request, Model model){
// read the request parameter from the html form
String theName = request.getParameter("studentName");
// convert the data to all caps
theName = theName.toUpperCase();
// create the msg
String result = "Yo! Hello! " + theName;
// add the msg to the model
model.addAttribute("msg", result);
return "helloworld";
}
  1. Add the attribute to the view page.
1
2
3
4
<br><br>
Student Name: ${param.studentName}
<br><br>
The message: ${msg}

Note: The attribute name msg has to match the one in the controller method.

  1. Modify the JSP page name in the starting view page.
1
2
3
<form action="processFormVersionTwo" method="GET">
...
</form>

Request Params and Request Mappings

Bind Request Params

Instead of using HttpServletRequest , use a different technique to read the form data. - An annotation

@RequestParam("paramName")

Process

Spring will read the param from the form request: studentName

Bind it the the variable that is given this annotation.

1
2
3
4
5
6
@RequestMapping("..")
public String letsShoutDudeTwo(
@RequestParam("studentName")
String theName,
Model model){
}

Controller Level Request Mapping

Add a controller level mapping to resolve some mapping conflicts.

1
2
3
4
@Controller
@RequestMapping("/hello")
public class HelloWorldController {
}

Form Tags and Data Binding

Form Page Structure

1
2
3
4
5
<html>
... Regular html ...
... Spring MVC Form Tags ...
... More html ...
</html>

Reference Spring MVC Form Tags

Specify the namespace st beginning of the JSP file.

1
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

Text Fields

Show Form

In Spring Controller:

  • Before showing the form, we need to add a model attribute.
  • This is the bean that will hold form data for the data binding.

Development Process

  1. Create the student class and generate the getters and setters.
  2. Create the student controller class.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping("/student")
public class StudentController {

@RequestMapping("/showForm")
public String showForm(Model theModel){
// create a new student object
Student theStudent = new Student();
// add the student object to model
theModel.addAttribute("student", theStudent);
return "student-form";
}
}
  1. Create the HTML form.
1
2
3
4
5
6
7
8
9
10
11
<form:form action="processForm" modelAttribute="student">
First Name : <form:input path="firstName"/>
<br><br>
Last Name : <form:input path="lastName"/>
<br><br>
<input type="submit" value="Submit"/>
<br><br>
<form:select path="country">
<form:options items="${student.countryOptions}"/>
</form:select>
</form:form>
  1. Create form processing code.
1
2
3
4
5
6
@RequestMapping("/processForm")
public String processForm(@ModelAttribute("student") Student theStudent){
// log the input data
System.out.println("The Student First Name: " + theStudent.getFirstName() + " " + theStudent.getLastName());
return "student-confirmation";
}
  1. Create confirmation page.
1
2
3
4
5
6
<body>
<%-- Calls the student.getFirstName() --%>
The Student is Confirmed: ${student.firstName} ${student.lastName}
<br><br>
The Country: ${student.country}
</body>

Use properties file to load data

  1. Create a properties file to store the countries.

New text file: WEB-INF/countries.properties

1
2
3
4
5
CN=China
BR=Brazil
FR=France
CO=Colombia
IN=India
  1. Update header section for Spring config file. Replace the header content in the spirng-mvc-demo-servlet.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
  1. Load country options properties in the config file. Bean id = countryOptions
1
<util:properties id="countryOptions" location="classpath:../countries.properties" />
  1. Import the java.util.Map in StudentController.java. And inject the properties into the StudentController.java.
1
2
@Value("#{countryOptions}")
private Map<String, String> countryOptions;
  1. Add country Options to the Spring MVC model and specify the attribute name: theCountryOptions
1
2
// add the country options to the model 
theModel.addAttribute("theCountryOptions", countryOptions);
  1. Update the JSP view page to use the model attribute for drop down list.
1
2
3
<form:select select path="country">
<form:options items="${countryOptions}"/>
</form:select>

Radio Buttons

  1. Update HTML form.
  2. Update Student Class - add getter and setter.
  3. Update confirmation page.

Example:

1
Java <form:radiobutton path="favoriteLanguage" value="Java"/>

Check Box

Example:

1
2
Linux <form:checkbox path="operatingSystem" value="Linux"/>
Mac OS <form:checkbox path="operatingSystem" value="MacOS"/>

Include jstl.core.jar in your lib. And display the String array using:

1
2
3
<c:forEach var="temp" items="${student.operatingSystem}">
<li>${temp}</li>
</c:forEach>

Spring MVC Form Validation

Overview

Java’ Standard Bean Validation API

Spring supports the Bean Validation API. - Preferred method.

Features

  • Required
  • Length
  • Numbers
  • Regular Expressions
  • Custom Validation

Annotations

  • @NotNull
  • @Min
  • @Max
  • @Size
  • @Pattern
  • @Future / @Past

Setup Dev Env

Go to Hibernate and click on Validator.

Download the zip file and unzip it.

Copy and paste the three jar files in ./dist and four jar files in ./dist/required to WEB-INF/lib

Hibernate JARs

Required Field

  1. Add validation rule to customer class.

    1
    2
    3
    4
    private String firstName;
    @NotNull(message="is required")
    @Size(min = 1, message="is required")
    private String lastName;
  2. Display error msg to the HTML form.

    1
    2
    3
    <br><br>
    Last Name(*): <form:input path="lastName"/>
    <form:errors path="lastName" cssClass="error"/>
  3. Perform validation in Controller Class.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RequestMapping("/showForm")
    public String showForm(Model theModel){
    theModel.addAttribute("customer", new Customer());
    return "customer-form";
    }
    @RequestMapping("/processForm")
    public String processForm(
    // @Valid performs the validation rules on customer object
    // Results of validation placed in the Binding Result.
    @Valid @ModelAttribute("customer") Customer theCustomer,
    BindingResult theBindingResult){
    if(theBindingResult.hasErrors()){
    return "customer-form";
    }else{
    return "customer-confirmation";
    }
    }
  4. Update Confirmation page.

    1
    2
    3
    <body>
    The Customer is Confirmed: ${customer.firstName} ${customer.lastName}
    <br><br>

    @InitBinder

@InitBinder works as a pre-processor.

It will pre-process each web request to the controller.

Method with this annotation will be executed.

1
2
3
4
5
6
7
8
// add an initbinder .. to convert trim input strings.
// remove the leading and trailing whitespace
// resolve issue for our validation
@InitBinder
public void initBinder(WebDataBinder dataBinder){
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
  • WebDataBinder
  • StringTrimmerEditor - A class defined in the Spring API, removes the leading and trailing white space.
  • new StringTrimmerEditor(true) - Trim the String to null if it’s all white space.
  • dataBinder.registerCustomEditor(String.class, stringTrimmerEditor); - for every String class, apply StringTrimmerEditor

Validate Number Range

1
2
3
@Min(value = 0,message = "must be greater than or equal to zero.")
@Max(value = 10, message = "must be less than or equal to 10")
private int freePasses;

Validate Regular Expression

1
2
@Pattern(regexp = "^[a-zA-Z0-9]{5}", message = "only 5 chars/digits")
private String postalCode;

How to make the Integer Field required

1
private Integer freePasses;

Solution: Convert the int variable to Integer, otherwise there will be a conversion error,

The trimmer will trime the string to null string if it is all white spaces, so the string cannot be converted directly to primitive type int, and we should use Integer type, which can be converted from the String type.

How to handle String input for Integer Field

Problem

Process
  1. Create custom error message.

    1
    typeMismatch.customer.freePasses=Invalid Number

    Create a folder resources under src path, and create a message.properties file.

  2. Load custom error message to the spring-servlet.xml config file.

    1
    2
    3
    4
    5
    <!--	Load custom message resources-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames" value="resources.message" />
    </bean>

    Deep Dive

Below is the BindingResult with Error Msg:

1
2
Binding Result: org.springframework.validation.BeanPropertyBindingResult: 1 error
Field error in object 'customer' on field 'freePasses': rejected value [dfsdsfadf]; codes [typeMismatch.customer.freePasses,typeMismatch.freePasses,typeMismatch.java.lang.Integer,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customer.freePasses,freePasses]; arguments []; default message [freePasses]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Integer' for property 'freePasses'; nested exception is java.lang.NumberFormatException: For input string: "dfsdsfadf"]

According the Error Msg, we can custom different typeMismatch in the messages.properties:

  • typeMismatch.customer.freePasses
  • typeMismatch.freePasses
  • typeMismatch.java.lang.Integer
  • typeMismatch

Custom Validation

Example

  • Course Code must start with “LUV”
  • Spring MVC calls the custom validation
  • The validation returns a boolean value for pass or fail (T/F)

Custom Java Annotation

1
@CourseCode(value="LUV", message="must start with LUV")

Process

  1. Create @CourseCode annotation.

    • Some annotations of the custome Annotation.
    1
    @Constraint(validatedBy = CourseCodeConstraintValidator.class)

    CourseCodeConstraintValidator - Helper class that contains business rules or validation logic.

    1
    @Target({ElementType.METHOD, ElementType.FIELD }) 

    Can apply our annotation to a method or a field.

    1
    @Retention(RetentionPolicy.RUNTIME) 

    Retain this annotation in the Java class file. Process it at runtime.

    • Define attribtues and set default values.
    1
    2
    3
    4
    5
    6
    7
    8
    // define default course code
    String value() default "LUV";
    // define the default error msg
    String message() default "Must start with LUV";
    // define default groups
    Class<?>[] groups() default {};
    // define default payloads
    Class<? extends Payload>[] payload() default {};
  2. Develop CourseCodeConstraintValidator.

    1
    2
    3
    public class CourseCodeConstraintValidator
    implements ConstraintValidator<CourseCode, String> {
    }
    • CourseCode - Annotation Name
    • String - Type of data to validate against
    1
    2
    3
    4
    5
    private String coursePrefix;
    @Override
    public void initialize(CourseCode theCourseCode) {
    coursePrefix = theCourseCode.value();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public boolean isValid(String theCode, ConstraintValidatorContext theConstraintValidatorContext) {
    if(theCode != null){
    return theCode.startsWith(coursePrefix);
    }else{
    return true;
    }
    }
    • Spring MVC will call the isValid() method at runtime.

    • theCode - HTML form data entered by the user.

    • ConstraintValidatorContext - Helper class for additional error messages.

    • Method Body - Validation logic - Test if the form data starts with the provided course prefix.

Reference: Udemy, Spring & Hibernate for Beginners (including SpringBoot)