- Overview
- Spring MVC Configuration
- Controllers and Views
- Request Params and Request Mappings
- Form Tags and Data Binding
- Form Validation
The Model-View-Controller (MVC) is an architectural pattern that separates an application into three main logical components: the Model, the View, and the Controller. Each of these components are built to handle specific development aspects of an application.
- Model corresponds to all the data-related logic that the user works with.
- View is used for all the UI logic of the application.
- Controller act as an interface between Model and View components to process all the business logic and incoming requests, manipulate data using the Model component and interact with the Views to render the final output.
Spring MVC is a framework based on Model-View-Controller (MVC) design pattern for building web applications in Java. It can leverages features of the Core Spring Framework (IoC, DI).
The figure shows the Spring MVC framework components:
- Front Controller
- Part of the Spring Framework and known as
DispatcherServlet
. - Already developed by Spring Dev Team.
- Part of the Spring Framework and known as
- Controller
- Code created by developers (us).
- Contains the business logic. e.g. handling the requests, storing/retrieving data, placing data in models
- Send to appropriate view template.
- Model
- Contains our data.
- Store/retreieve data via backend systems (database, web service, etc...).
- Data can be any Java object/collection.
- View Template
- Spring MVC is flexible and supports many view templates.
- The most common is JSP + JSTL.
- Developers create the pages for displaying data.
- Other templates supported: Thymeleaf, Groovy, Velocity, Freemarker, etc...
- Add configurations to file:
WEB-INF/web.xml
- Configure Spring MVC Dispatcher Servlet
- Setup URL mapping to Spring MVC Dispatcher Servlet
- Add configurations to file:
WEB-INF/spring-mvc-demo-servlet.xml
- Add support for Spring component scanning.
- Add support for conversion, formatting and validation.
- Configure Spring MVC View Resolver.
Check the Setup Development Environment: Spring MVC Project for more information.
What we're gonna do here is actually setting up a request mapping for a given path. This request path should be sent to the Controller for forwarding it over to a view template:
- Request Path:
/
. - Controller:
HomeController
. - View Template:
main-menu.jsp
.
- Create Controller Class.
- Define Controller Method.
- Add Request Mapping to Controller Method.
- Return View Name.
- Develop View Page.
Let's create a new package named com.luv2code.springdemo.mvc
and create the Controller class HomeController
:
@Controller
public class HomeController {
@RequestMapping("/")
public String showPage() {
return "main-menu";
}
}
- Annotate class with
@Controller
annotation which inherits from@Component
supports scanning. - Spring MVC is flexible and we can use any method name.
- Use
@RequestMapping
annotation for adding request mapping. - Spring will use the information from the configuration file for finding the view page.
- Example Configuration File.
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/" /> <property name="suffix" value=".jsp" /> </bean>
- How Spring find the Template View?
/WEB-INF/view/ main-menu .jsp [‾‾‾‾‾‾‾‾‾‾‾‾‾‾][‾‾‾‾‾‾‾‾‾][‾‾‾‾] prefix view name suffix
- Example Configuration File.
Add main-menu.jsp
file inside the /WEB-INF/view/
folder:
<!DOCTYPE html>
<html>
<body>
<h2>Spring MVC Demo - Home Page</h2>
</body>
</html>
Now, right click the project name and choose [Run As] > [Run on Server].
We're going to define two methods inside the HelloWorldController
:
@Controller
public class HelloWorldController {
// need a controller method to show the initial HTML form
@RequestMapping("/showForm")
public String showForm() {
return "helloworld-form";
}
// need a controller method to process the HTML form
@RequestMapping("/processForm")
public String processForm() {
return "helloworld";
}
}
Add helloworld-form
file inside the /WEB-INF/view/
folder:
<!DOCTYPE html>
<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 helloworld
file inside the /WEB-INF/view/
folder:
<!DOCTYPE html>
<html>
<body>
<h2>Hello World of Spring!</h2>
<div>
Student name: ${param.studentName}
</div>
</body>
</html>
The Model is a container for our application data. We can put anything in the model in the Controller.
@Controller
public class HelloWorldController {
@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 message
String result = "Yo!" + theName;
// add message to the model
model.addAttribute("message", result);
return "helloworld";
}
}
Now, the view template can get data with given attribute name from the model:
<!DOCTYPE html>
<html>
<body>
<h2>Hello World of Spring!</h2>
<div>
Student name: ${param.studentName}
<br><br>
The message: ${message}
</div>
</body>
</html>
Spring has a special annotation called RequestParam
which allow us to read form data and binds it to a parameter automatically.
@Controller
public class HelloWorldController {
@RequestMapping("/processFormVersionThree")
public String processFormVersionThree(@RequestParam("studentName") String theName, Model model) {
// convert the data to all caps
theName = theName.toUpperCase();
// create the message
String result = "Hey My Friend from v3! " + theName;
// add message to the model
model.addAttribute("message", result);
return "helloworld";
}
}
We can also add @RequestMapping
annotation to a controller. It serves as parent mapping for controller and all the request mappings on methods in the controller are relative.
@Controller
@RequestMapping("/hello")
public class HelloWorldController {
// need a controller method to show the initial HTML form
@RequestMapping("/showForm")
public String showForm() {
return "helloworld-form";
}
}
Spring MVC Form Tags are the building block for a webpage and these Form Tags are configurable and resuable. The Form Tags can make use of data binding and automatically set / retrieve data from a Java bean.
Form Tags | Description |
---|---|
form:form |
main form container |
form:input |
text field |
form:textarea |
multi-line text field |
form:checkbox |
check box |
form:radiobutton |
radio buttons |
form:select |
drop-down list |
more .... |
Just specify the Spring namespace at beginning of JSP file for referencing Spring MVC Form Tags:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
Before we shown the form, we must add model attributes. These are the beans that will hold form data for data binding. Let's create the StudentController
class:
@Controller
@RequestMapping("/student")
public class StudentController {
@RequestMapping("/showForm")
public String showForm(Model theModel) {
// create a student object
Student theStudent = new Student();
// add student object to the model
theModel.addAttribute("student", theStudent);
return "student-form";
}
@RequestMapping("/processForm")
public String processForm(@ModelAttribute("student") Student theStudent) {
// log the input data
System.out.println("theStudent: " + theStudent.getFirstName() + " " + theStudent.getLastName());
return "student-confirmation";
}
}
Add the form view student-form.jsp
file:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Student Registration Form</title>
</head>
<body>
<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" />
</form:form>
</body>
</html>
Add the form view student-confirmation.jsp
file:
<!DOCTYPE html>
<html>
<head>
<title>Student Confirmation</title>
</head>
<body>
The student is confirmed: ${student.firstName} ${student.lastName}
</body>
</html>
Note that the Spring will call student.getFirstName()
for ${student.firstName}
inside the view.
<html>
<body>
<form:form action="processForm" modelAttribute="student">
...
Country:
<form:select path="country">
<form:option value="Brazil" label="Brazil" />
<form:option value="France" label="France" />
<form:option value="Germany" label="Germany" />
<form:option value="India" label="India" />
</form:select>
...
</form:form>
</body>
</html>
Let's add the getCountryOptions()
for generating collection of countrys:
package com.luv2code.springdemo.mvc;
import java.util.LinkedHashMap;
public class Student {
private String firstName;
private String lastName;
private String country;
private LinkedHashMap<String, String> countryOptions;
public Student() {
// populate country options: used ISO country code
countryOptions = new LinkedHashMap<>();
countryOptions.put("BR", "Brazil");
countryOptions.put("FR", "France");
countryOptions.put("DE", "Germany");
countryOptions.put("IN", "India");
countryOptions.put("US", "United States of America");
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public LinkedHashMap<String, String> getCountryOptions() {
return countryOptions;
}
}
Then we can use <form:options items="${student.countryOptions}" />
for getting data from that method:
<html>
<body>
<form:form action="processForm" modelAttribute="student">
...
Country:
<form:select path="country">
<form:options items="${student.countryOptions}" />
</form:select>
...
</form:form>
</body>
</html>
Add following code to student-form.jsp
:
<html>
<body>
<form:form action="processForm" modelAttribute="student">
...
Favorite Languages:
Java <form:radiobutton path="favoriteLanguage" value="Java" />
C# <form:radiobutton path="favoriteLanguage" value="C#" />
PHP <form:radiobutton path="favoriteLanguage" value="PHP" />
Ruby <form:radiobutton path="favoriteLanguage" value="Ruby" />
...
</form:form>
</body>
</html>
Add following code to student-form.jsp
:
<html>
<body>
<form:form action="processForm" modelAttribute="student">
...
Operation Systems:
Linux <form:checkbox path="operatingSystems" value="Linux" />
macOS <form:checkbox path="operatingSystems" value="macOS" />
Windows <form:checkbox path="operatingSystems" value="Windows" />
...
</form:form>
</body>
</html>
Add following code to student-confirmation.jsp
:
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<body>
...
Operating Systems:
<ul>
<c:forEach var="temp" items="${student.operatingSystems}">
<li>${temp}</li>
</c:forEach>
</ul>
...
</body>
</html>
Refer to Unknown tag (c:foreach). In eclipse, just add <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
in front of student-confirmation.jsp
for the issue "Unkown tag... <c:forEach... />".
Java has a standard Bean Validation API which defines a metadata model and API for entity validation:
Annotation | Description |
---|---|
@NotNull |
checks that the annotated value is not null |
@Min |
must be a number greather than value |
@Max |
must be a number less than value |
@Size |
size must match the given size |
@Pattern |
must match a regular expression pattern |
@Future /@Past |
date must be in future or past of given data |
others... |
- Spring 4 and higher supports Bean Validation API.
- Preferred for validation when building Spring applications.
The Java's standard Bean Validation API (JSR-303) is only a specification and we need a implementation for it. The Hibernate Team have a fully compliant JSR-303 implementation: Hibernate Validator. There are something about versions with Hibernate Validator:
- The Jakarta EE is the community version of Java EE (rebranded, relicensed). And Jakarta EE doesn't replace Java EE.
- Last version is Java EE 8 (August 2017).
- Jakarta EE is moving forward with Jakarta EE 9 (December 2020) and future releases.
- At the moment, main change with Jakarta EE is the package naming:
javax.*
>jakarta.*
.
- Hibernate Validator 7 is based on Jakarta EE 9.
- Spring 5 is still based on some components of Java EE (
javax.*
).
As a result, Spring 5 is not compatible with Hibernate Validator 7. And there are two releases of Hibernate Validator with the same features:
- Hibernate Validator 7 is for Jakarta EE 9 project.
- Hibernate Validator 6.2 is compatible with Spring 5.
- Visit Hibernate Validator website.
- Navigate to [Release] > [6.2] and click [Download the Zip archive].
- Unzip the file and copy the
*.jar
in./dist
and./lib/required
folders to the project's/WebContent/WEB-INF/lib
directory.
- Add Validation Rule to Customer Class.
- Display Error Messages on HTML Form.
- Perform Validation in the Controller Class.
- Update Confirmation Page.
public class Customer {
private String firstName;
@NotNull(message = "is required")
@Size(min = 1, message = "is required")
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Create customer-form.jsp
for displaying error messages:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Customer Registration Form</title>
<style>
.error {
color: red;
}
</style>
</head>
<body>
<i>Fill out the form. Asterisk (*) means required.</i>
<br>
<form:form action="processForm" modelAttribute="customer">
First name: <form:input path="firstName" />
<br>
Last name (*): <form:input path="lastName" />
<form:errors path="lastName" cssClass="error" />
<br>
<input type="submit" value="Submit" />
</form:form>
</body>
</html>
@Controller
@RequestMapping("/customer")
public class CustomerController {
@RequestMapping("/showForm")
public String showForm(Model theModel) {
theModel.addAttribute("customer", new Customer());
return "customer-form";
}
@RequestMapping("/processForm")
public String processForm(@Valid @ModelAttribute("customer") Customer theCustomer, BindingResult theBindingResult) {
if (theBindingResult.hasErrors()) {
return "customer-form";
} else {
return "customer-confirmation";
}
}
}
The @InitBinder
annotation works as a pre-processor, and it will pre-process each web request to our controller.
@Controller
@RequestMapping("/customer")
public class CustomerController {
// add an initBinder to convert trim input strings
// remove leading and trailing whitespace
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
...
}
We've use @InitBinder
to trim Strings (removing leading and trailing white space).
public class Customer {
...
// Number Range Validation Rule
@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;
...
// Regular Expression Validation Rule
@Pattern(regexp = "^[a-zA-Z0-9]{5}", message = "only 5 chars/digits")
private String postalCode;
...
}
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<body>
<form:form action="processForm" modelAttribute="customer">
...
Free passes: <form:input path="freePasses" />
<form:errors path="freePasses" cssClass="error" />
Postal Code: <form:input path="postalCode" />
<form:errors path="postalCode" cssClass="error" />
...
</form:form>
</body>
</html>
- Create Custom Error Message:
src/resources/messages.properties
. - Load Custom Messages Resource in Spring Config File:
WebContent/WEB-INF/spring-mvc-demo-servlet.xml
.
typeMismatch . customer . freePasses = Invalid number
[‾‾‾‾‾‾‾‾‾‾‾‾] [‾‾‾‾‾‾‾‾] [‾‾‾‾‾‾‾‾‾‾] [‾‾‾‾‾‾‾‾‾‾‾‾‾‾]
Error Type Attribute Field Name Value
Add following bean property in spring-mvc-demo-servlet.xml
:
<beans ...>
...
<!-- Load custom message resources -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames" value="resources/messages" />
</bean>
...
</beans>
For custom validation, we will create a Custom Java Annotation by following steps:
- Create Custom Validation Rule.
- Add Validation Rule to Class.
- Display Error Messages on HTML Form.
- Update Confirmation Page.
Create a new package com.luv2code.springdemo.mvc.validation
and add an annotation named CourseCode
:
@Constraint(validatedBy = CourseCodeConstraintValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseCode {
// define default course code
public String value() default "LUV";
// define default error message
public String message() default "must start with LUV";
}
The CourseCodeConstraintValidator
class should be a helper class that contains business rules / validation logic:
public class CourseCodeConstraintValidator implements ConstraintValidator<CourseCode, String> {
private String coursePrefix;
@Override
public void initialize(CourseCode theCourseCode) {
coursePrefix = theCourseCode.value();
}
@Override
public boolean isValid(String theCode, ConstraintValidatorContext theConstraintValidatorContext) {
boolean result;
if (theCode != null) {
result = theCode.startsWith(coursePrefix);
} else {
result = true;
}
return result;
}
}
public class Customer {
...
@CourseCode(value = "TOPS", message = "must start with TOPS")
private String courseCode;
...
}