Skip to main content

Spring MVC Architecture

Introduction

Spring MVC (Model-View-Controller) is a powerful and flexible web application framework built on top of the core Spring framework. It provides a clean separation of concerns through its architectural pattern, making it easier to develop, test, and maintain web applications. Whether you're building a simple form-based application or a complex enterprise system, understanding the Spring MVC architecture is essential for Java developers.

In this guide, we'll explore the fundamental components of Spring MVC, how they interact with each other, and how data flows through the system. By the end, you'll have a solid understanding of this architecture and be ready to implement it in your own projects.

Core Components of Spring MVC

The Spring MVC framework is built around the following key components:

1. DispatcherServlet

The DispatcherServlet is the central component of Spring MVC. It acts as the front controller, handling all incoming HTTP requests and delegating them to appropriate handlers. Think of it as the traffic director of your application.

2. Handler Mappings

Handler mappings help the DispatcherServlet determine which controller should process a specific request. They map URLs to controllers.

3. Controllers

Controllers process requests and return an appropriate response. They contain the application logic and determine which model data to gather and which view to render.

4. Model

The Model represents the application's data and business logic. It's essentially a container for data that will be presented in the view.

5. ViewResolver

The ViewResolver determines which view to use based on a logical view name returned by the controller.

6. View

The View renders the model data, usually as HTML, but it could also be XML, JSON, PDF, etc.

Request Processing Flow

Let's walk through how a typical request flows through the Spring MVC architecture:

  1. The client sends an HTTP request to the server
  2. The DispatcherServlet receives the request
  3. The DispatcherServlet consults handler mappings to identify the appropriate controller
  4. The controller processes the request, interacts with the model, and returns a logical view name
  5. The ViewResolver resolves the logical view name to an actual view
  6. The view renders the model data
  7. The response is sent back to the client

Implementation Example

Let's implement a simple Spring MVC application to understand how these components work together:

Setting up Dependencies

First, we need to include the necessary dependencies in our Maven pom.xml file:

xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>

Configuring the DispatcherServlet

Next, we need to configure the DispatcherServlet in our web.xml file:

xml
<web-app>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

Alternatively, with Java configuration:

java
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}

@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}

Creating a Simple Controller

Let's create a simple controller that demonstrates the MVC pattern:

java
@Controller
public class HelloController {

@RequestMapping("/hello")
public String hello(Model model, @RequestParam(value="name", required=false, defaultValue="World") String name) {
model.addAttribute("name", name);
return "hello"; // This is the logical view name
}
}

Configuring the ViewResolver

Now, let's configure a ViewResolver in our Spring configuration:

java
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.controllers")
public class WebConfig implements WebMvcConfigurer {

@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
}

Creating the View

Finally, let's create our view. Create a file called hello.jsp in the /WEB-INF/views/ directory:

html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello Spring MVC</title>
</head>
<body>
<h1>Hello, ${name}!</h1>
</body>
</html>

Testing the Application

When a user navigates to /hello?name=John, the following happens:

  1. The DispatcherServlet receives the request
  2. It maps the URL /hello to the hello method in HelloController
  3. The controller processes the request, adds "John" to the model, and returns the logical view name "hello"
  4. The ViewResolver resolves "hello" to /WEB-INF/views/hello.jsp
  5. The view renders the model data, displaying "Hello, John!"
  6. The response is sent back to the client

Spring MVC Annotations

Spring MVC provides several annotations that simplify development:

  • @Controller: Marks a class as a web controller
  • @RequestMapping: Maps web requests to handler methods
  • @GetMapping, @PostMapping, etc.: Shorthand for @RequestMapping with specific HTTP methods
  • @RequestParam: Binds request parameters to method parameters
  • @PathVariable: Binds URI template variables to method parameters
  • @ModelAttribute: Binds form data to a model object
  • @ResponseBody: Indicates that the return value should be written directly to the response body

Example of a controller using these annotations:

java
@Controller
@RequestMapping("/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping
public String listUsers(Model model) {
model.addAttribute("users", userService.getAllUsers());
return "user/list";
}

@GetMapping("/{id}")
public String viewUser(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.getUserById(id));
return "user/view";
}

@GetMapping("/new")
public String newUserForm(Model model) {
model.addAttribute("user", new User());
return "user/form";
}

@PostMapping
public String saveUser(@ModelAttribute User user) {
userService.saveUser(user);
return "redirect:/users";
}
}

Real-World Application: Building a Task Manager

Let's build a simple task manager application to demonstrate Spring MVC in a practical scenario.

Model Class

java
public class Task {
private Long id;
private String title;
private String description;
private boolean completed;

// Getters and setters omitted for brevity
}

Service Layer

java
@Service
public class TaskService {

private final List<Task> tasks = new ArrayList<>();
private Long nextId = 1L;

public List<Task> getAllTasks() {
return new ArrayList<>(tasks);
}

public Task getTaskById(Long id) {
return tasks.stream()
.filter(task -> task.getId().equals(id))
.findFirst()
.orElse(null);
}

public Task createTask(Task task) {
task.setId(nextId++);
tasks.add(task);
return task;
}

public boolean updateTask(Task task) {
for (int i = 0; i < tasks.size(); i++) {
if (tasks.get(i).getId().equals(task.getId())) {
tasks.set(i, task);
return true;
}
}
return false;
}

public boolean deleteTask(Long id) {
return tasks.removeIf(task -> task.getId().equals(id));
}
}

Controller

java
@Controller
@RequestMapping("/tasks")
public class TaskController {

private final TaskService taskService;

public TaskController(TaskService taskService) {
this.taskService = taskService;
}

@GetMapping
public String listTasks(Model model) {
model.addAttribute("tasks", taskService.getAllTasks());
return "task/list";
}

@GetMapping("/{id}")
public String viewTask(@PathVariable Long id, Model model) {
model.addAttribute("task", taskService.getTaskById(id));
return "task/view";
}

@GetMapping("/new")
public String newTaskForm(Model model) {
model.addAttribute("task", new Task());
return "task/form";
}

@PostMapping
public String saveTask(@ModelAttribute Task task) {
if (task.getId() == null) {
taskService.createTask(task);
} else {
taskService.updateTask(task);
}
return "redirect:/tasks";
}

@GetMapping("/{id}/edit")
public String editTaskForm(@PathVariable Long id, Model model) {
model.addAttribute("task", taskService.getTaskById(id));
return "task/form";
}

@GetMapping("/{id}/delete")
public String deleteTask(@PathVariable Long id) {
taskService.deleteTask(id);
return "redirect:/tasks";
}
}

Views

For the task list (task/list.jsp):

html
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Task Manager</title>
</head>
<body>
<h1>Task Manager</h1>
<a href="${pageContext.request.contextPath}/tasks/new">Create New Task</a>

<table>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Completed</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<c:forEach items="${tasks}" var="task">
<tr>
<td>${task.id}</td>
<td>${task.title}</td>
<td>${task.completed ? 'Yes' : 'No'}</td>
<td>
<a href="${pageContext.request.contextPath}/tasks/${task.id}">View</a>
<a href="${pageContext.request.contextPath}/tasks/${task.id}/edit">Edit</a>
<a href="${pageContext.request.contextPath}/tasks/${task.id}/delete">Delete</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>

Advanced Concepts in Spring MVC

1. Exception Handling

Spring MVC provides several ways to handle exceptions:

java
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(TaskNotFoundException.class)
public ModelAndView handleTaskNotFoundException(TaskNotFoundException ex) {
ModelAndView mav = new ModelAndView("error/task-not-found");
mav.addObject("message", ex.getMessage());
return mav;
}

@ExceptionHandler(Exception.class)
public ModelAndView handleGenericException(Exception ex) {
ModelAndView mav = new ModelAndView("error/generic");
mav.addObject("message", "An unexpected error occurred");
return mav;
}
}

2. Interceptors

Interceptors allow you to intercept requests before they reach controllers or after controllers process them:

java
public class LoggingInterceptor implements HandlerInterceptor {

private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.info("Request URL: {}", request.getRequestURL());
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.info("Response status: {}", response.getStatus());
}
}

Register the interceptor in your configuration:

java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggingInterceptor());
}
}

3. Form Validation

Spring MVC integrates with Hibernate Validator for form validation:

java
public class Task {
private Long id;

@NotBlank(message = "Title cannot be empty")
@Size(min = 3, max = 100, message = "Title must be between 3 and 100 characters")
private String title;

@Size(max = 500, message = "Description cannot be longer than 500 characters")
private String description;

private boolean completed;

// Getters and setters
}

In the controller:

java
@PostMapping
public String saveTask(@Valid @ModelAttribute Task task, BindingResult result) {
if (result.hasErrors()) {
return "task/form";
}

if (task.getId() == null) {
taskService.createTask(task);
} else {
taskService.updateTask(task);
}
return "redirect:/tasks";
}

Summary

In this guide, we've explored the Spring MVC architecture in detail:

  1. We learned about the core components: DispatcherServlet, Handler Mappings, Controllers, Model, ViewResolver, and View
  2. We understood the request processing flow through the Spring MVC framework
  3. We implemented a basic Spring MVC application and a more complex Task Manager application
  4. We explored advanced concepts like exception handling, interceptors, and form validation

Spring MVC provides a powerful, flexible framework for building web applications in Java. Its clean separation of concerns makes it easier to develop, test, and maintain your code. The architecture is designed to be extensible, allowing you to customize almost every aspect of the framework to meet your specific requirements.

Additional Resources

  1. Spring MVC Official Documentation
  2. Spring MVC Tutorial - Baeldung
  3. Spring MVC Test Framework

Practice Exercises

  1. Extend the Task Manager application to include user authentication
  2. Add validation to ensure task titles are unique
  3. Implement RESTful API endpoints that return JSON instead of HTML views
  4. Add pagination to the task list page
  5. Implement a search feature to find tasks by title or description

By mastering Spring MVC architecture, you'll be well-equipped to build robust, maintainable web applications that can scale with your needs.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)