Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

A Practical Guide to Building Web Applications with Spring MVC

Tech 1

Project Dependencies and Configuration

Add the required Spring MVC JAR files to your project's classspath, or include the corresponding Maven/Gradle dependencies.

Register the DispatcherServlet in web.xml:

<servlet>
    <servlet-name>springDispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-servlet.xml</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>springDispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Create the Spring MVC configuration file spring-servlet.xml under the classpath:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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">

    <context:component-scan base-package="com.example.controller"/>
    <mvc:default-servlet-handler/>
    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

First Controller and View

Create a package com.example.controller and add a controller class:

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/demo")
public class DemoController {

    @RequestMapping("/welcome")
    public String showWelcome() {
        return "welcome";
    }
}

Place a JSP file welcome.jsp inside /WEB-INF/views/. Access the page via http://localhost:8080/project/demo/welcome.

Essential Annotations

  • @Controller: Marks a class as a Spring MVC controller.
  • @RequestMapping: Maps HTTP requests to handler methods.
  • @RequestBody: Binds the HTTP request body to a method parameter using an appropriate message converter.
  • @ResponseBody: Serializes the return value directly to the HTTP response body.
  • @PathVariable: Extracts values from the URI template.
  • @RequestParam: Binds a request parameter to a method parameter.
  • @ModelAttribute: Prepares model data or binds request parameters to an object.
  • @InitBinder: Registers custom property editors for data binding.
  • @ExceptionHandler: Defines a method to handle exceptions thrown by controller methods.
  • @ControllerAdvice: Global exception handling and model attribute configuration.

Automatic Parameter Binding

Spring MVC automatically maps query parameters to method arguments of the same name:

@RequestMapping("/employee")
public String employeeInfo(String name, int age) {
    System.out.println("Name: " + name + ", Age: " + age);
    return "employeeView";
}

Binding Request Parameters to a Model Object

Create a model class:

package com.example.model;

public class Employee {
    private String fullName;
    private int experience;

    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public int getExperience() { return experience; }
    public void setExperience(int experience) { this.experience = experience; }
}

Handler method:

@RequestMapping("/register")
public String registerEmployee(Employee emp) {
    System.out.println(emp.getFullName() + " has " + emp.getExperience() + " years");
    return "success";
}

Custom Date Binding with @InitBinder

@InitBinder
public void configureBinding(WebDataBinder binder) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    dateFormat.setLenient(false);
    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}

@RequestMapping("/schedule")
public String scheduleMeeting(Date meetingDate) {
    System.out.println("Meeting on: " + meetingDate);
    return "scheduled";
}

Sharing Data with the View

@RequestMapping("/profile")
public String showProfile(Map<String, Object> model) {
    Employee emp = new Employee();
    emp.setFullName("Alice");
    emp.setExperience(5);
    model.put("employee", emp);
    return "profile";
}

AJAX Requests

Server-side handler:

@RequestMapping("/fetchEmployee")
public void fetchEmployee(String name, PrintWriter writer) {
    writer.write("Found: " + name);
}

Client-side jQuery call:

$.post("demo/fetchEmployee", { name: $("#inputName").val() }, function (response) {
    alert(response);
});

Redirects

@RequestMapping("/oldPage")
public String redirectToNew() {
    return "redirect:/demo/welcome";
}

File Upload Implementation

Include Apache Commons FileUpload JARs. Configure a multipart resolver in Spring:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="52428800"/>
</bean>

Upload handler:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String handleUpload(HttpServletRequest request) throws Exception {
    MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    MultipartFile file = multipartRequest.getFile("uploadFile");
    String originalName = file.getOriginalFilename();
    String path = request.getServletContext().getRealPath("/") + "uploads/" + originalName;
    file.transferTo(new File(path));
    return "uploadSuccess";
}

HTML form:

<form action="demo/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="uploadFile" />
    <input type="submit" value="Upload" />
</form>

Explicit Parameter Naming with @RequestParam

@RequestMapping("/search")
public String searchEmployees(@RequestParam("deptId") int departmentId,
                              @RequestParam("keyword") String keyword) {
    System.out.println("Dept: " + departmentId + ", Keyword: " + keyword);
    return "searchResults";
}

RESTful Endpoints

@RestController
@RequestMapping("/api/employees")
public class EmployeeApiController {

    @GetMapping("/{id}")
    public Employee getEmployee(@PathVariable int id) {
        // retrieve and return employee
    }

    @PostMapping
    public Employee createEmployee(@RequestBody Employee newEmployee) {
        // save and return
    }

    @PutMapping("/{id}")
    public Employee updateEmployee(@PathVariable int id, @RequestBody Employee employee) {
        // update and return
    }

    @DeleteMapping("/{id}")
    public void deleteEmployee(@PathVariable int id) {
        // delete
    }
}

For HTML forms, enable HiddenHttpMethodFilter in web.xml:

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Use hidden _method field in forms to simulate PUT or DELETE.

Returning JSON Responses

Add Jackson JARs. Annotate a method with @ResponseBody (or use @RestController):

@RequestMapping("/data")
@ResponseBody
public Employee getJsonData() {
    Employee emp = new Employee();
    emp.setFullName("Bob");
    emp.setExperience(3);
    return emp;
}

Exception Handling

Local exception handler inside a controller:

@ExceptionHandler
public ModelAndView handleErrors(Exception ex) {
    ModelAndView mv = new ModelAndView("errorPage");
    mv.addObject("exceptionMessage", ex.getMessage());
    return mv;
}

Global exception handler with @ControllerAdvice:

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler
    public ModelAndView handleAllExceptions(Exception ex) {
        ModelAndView mv = new ModelAndView("globalError");
        mv.addObject("error", ex);
        return mv;
    }
}

Declarative global mapping:

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="java.lang.NullPointerException">nullError</prop>
        </props>
    </property>
</bean>

Custom Interceptors

Implement HandlerInterceptor:

public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("Request URI: " + request.getRequestURI());
        return true;
    }
    // postHandle and afterCompletion can be left empty
}

Register the interceptor in the Spring MVC configuration:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/demo/**"/>
        <bean class="com.example.interceptor.LoggingInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

Form Validation and Internationalization

Add Hibernate Validator (hibernate-validator and validation-api JARs). Annotate the model:

public class User {
    @NotEmpty(message = "{NotEmpty.user.name}")
    private String name;

    @Past(message = "{Past.user.birthDate}")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthDate;

    // getters and setters
}

Configure a message source in the Spring configuration file:

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="messages"/>
</bean>

Create messages.properties and locale-specific files (e.g., messages_zh_CN.properties) with custom validation messages.

Controller methods:

@RequestMapping(value = "/register", method = RequestMethod.GET)
public String showForm(Map<String, Object> model) {
    model.put("user", new User());
    return "userForm";
}

@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processForm(@Valid User user, BindingResult result) {
    if (result.hasErrors()) {
        return "userForm";
    }
    return "registrationSuccess";
}

Use Spring Form tags in the JSP:

<form:form action="demo/register" modelAttribute="user">
    Name: <form:input path="name"/><form:errors path="name"/><br/>
    Birth Date: <form:input path="birthDate"/><form:errors path="birthDate"/><br/>
    <input type="submit" value="Register"/>
</form:form>

To display locale-dependent labels, use <fmt:message> tags and switch browser language settings.

Integrating Spring IOC with Spring MVC

To combine Spring's core container and MVC, define two separate component-scan configurations that avoid overlapping managed beans.

Create a parent application context (applicationContext.xml) for services and repositories, excluding controllers:

<context:component-scan base-package="com.example">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>

In the Spring MVC context (spring-servlet.xml), scan only for controlers:

<context:component-scan base-package="com.example" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>

Load the parent context through a ContextLoaderListener in web.xml:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

With this setup, a service class annotated with @Service can be autowired into any MVC controller.

Execution Flow

A typical request passes through the DispatcherServlet, which consults handler mappings to determine the appropriate controller method. After interceptors, data binding, and validations, the method executes and returns a view name or response body. The view resolver constructs the final view path, and the response is rendered back to the client.

Comparison with Struts2

  • Spring MVC is method-oriented; each handler method maps to a URL. Struts2 is class-oriented, where an action class typically handles multiple operations.
  • Spring MVC controllers can be singletons, which is the recommended practice. Struts2 action classes need to be prototypes because they carry per-request state via instance variables.
  • Struts2's built‑in UI tags can lead to slower rendering; using JSTL often yields better performance.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.