Showing posts with label spring mvc. Show all posts
Showing posts with label spring mvc. Show all posts

Monday, October 23, 2017

Sending form data as a custom object to a Spring MVC Controller


Form Data and Spring MVC Controllers

In a Spring MVC application, sending form data to the controller is quite well known and trivial. Typically all data gets sent as a bunch of key=value pairs. Each key is a request param which can be captured in the Controller using the @RequestParam("key") String key construct in the method signature.

If the number of fields are less, then you can use the request param construct to capture each form field, but if there are several form fields, then using this approach can create a unwieldy method signature and create room for error.

There are several ways to collect form data but in this post, I am going to address one specific use case.

Motivation for this post

Recently, someone at work asked me how to send data so that it was received by the Spring controller as a custom object/Pojo. I suggested using javascript to massage and format the data as desired and then send it.

Now using jquery forms on the browser side you can send data in any format after converting it to a JSON string.

For some business reasons, javascript was not an option. The HTML form had to be submitted as is using traditional http post method.

The purpose of this short blog post is to demonstrate how to send data from the browser without the help of javascript or any jquery plugins. This is typically useful when you have checkboxes and radio buttons in the UI and there is a relation between them.

Example

I have created an example UI which shows the kind of form that was to be submitted and then the controller code which processes the http post of this form.



As you can see, this form shows a list of car makes and their colors. The idea is that the user will check a checkbox for a car make and then select a color using the radio buttons.

Note that this form is rendered on the server side by reading the car makes and colors and then sent as a HTML page to the browser.

The HTML generated for this form is like this

 <form name="customObjectForm" id="customObjectForm" method="post" action="/submit">  
   <table class="table table-striped table-condensed table-bordered">  
     <thead>  
       <tr>  
         <th>Vehicle</th>  
         <th>Select</th>  
         <th>Color</th>  
       </tr>  
     </thead>  
     <tbody>  
       <tr>  
         <td>Toyota Camry</td>  
         <td><input type="checkbox" name="carModel" value="Camry" /></td>  
         <td>  
           <input type="radio" name="colors[Camry]" value="RED" /> RED &nbsp;  
           <input type="radio" name="colors[Camry]" value="YELLOW" /> YELLOW&nbsp;  
           <input type="radio" name="colors[Camry]" value="GREEN" /> GREEN&nbsp;  
         </td>  
       </tr>  
       <tr>  
         <td>Honda Civic</td>  
         <td><input type="checkbox" name="carModel" value="Honda" /></td>  
         <td>  
           <input type="radio" name="colors[Honda]" value="BLUE" /> BLUE&nbsp;  
           <input type="radio" name="colors[Honda]" value="BLACK" /> BLACK&nbsp;  
           <input type="radio" name="colors[Honda]" value="GREEN" /> GREEN&nbsp;  
         </td>  
       </tr>  
     </tbody>  
     <tfoot>  
       <tr><td colspan="3"><input type="Submit" name="submit" class="btn btn-primary" /></td></tr>  
     </tfoot>  
   </table>  
 </form>  

Domain model

If you examine the HTML, you will notice that we can infer a Key Value pair type relationship between the car make and the color options. To relate the car make to the colors, the name of the radio buttons is named like an associative array with the car make as the value of the colors variable. 

The car make checkboxes can be represented as a List in the domain model. If a user checks a checkbox then that checkbox value is submitted by the form, otherwise it is not.

Next, the row showing color options must be related to the car make in the domain model. To make that happen, the radio button selection can be submitted as a Map in the domain model.

So the car model can be a List<String> carMakes and the car colors can be a Map<String, String> colors where the key is the car make and the value is the actual selected color.

There can be a better domain model created and the HTML can be structured accordingly, but this is a quick example to show how Spring can convert browser submitted values in ready to use custom objects that you define.

Here is the CarMakeColorModel domain object / POJO.

package com.example.formobjects.model;

import java.util.List;
import java.util.Map;

public class CarMakeColorModel {

 // for the checkboxes for selecting the car makes
 private List<String> carMakes;

 // for the radio buttons to select the car color
 private Map<String, String> colors;

 public CarMakeColorModel() {
  // default constructor
  super();
 }

 public CarMakeColorModel(List<String> carMakes, Map<String, String> colors) {
  super();
  this.carMakes = carMakes;
  this.colors = colors;
 }

 public List<String> getCarMakes() {
  return carMakes;
 }

 public void setCarMakes(List<String> carMakes) {
  this.carMakes = carMakes;
 }

 public Map<String, String> getColors() {
  return colors;
 }

 public void setColors(Map<String, String> colors) {
  this.colors = colors;
 }

}


On the Controller side, use the @ModelAttribute annotation to make Spring collect all the form submitted values and populate the CarMakeColorModel pojo.

And here is the Spring Controller snippet which processes this HTML form.


package com.example.formobjects.controllers;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.example.formobjects.model.CarMakeColorModel;

/**
 * Controller to handle the form submission
 * 
 * @author <a href="mailto:sunitkatkar@gmail.com">Sunit Katkar</a>
 * @version 1.0
 * @since Oct 2017
 */
@Controller
public class FormCustomObjectController {

 @RequestMapping(value = "/", method = RequestMethod.GET)
 public String index(ModelMap modelMap) {
  return "index";
 }

 @RequestMapping(value = "/submit", method = RequestMethod.POST)
 public String submit(HttpServletRequest request, ModelMap modelMap,
   @ModelAttribute CarMakeColorModel carMakeColorModel) {

  List<String> carMakes = carMakeColorModel.getCarMakes();
  Map<String, String> carColors = carMakeColorModel.getColors();
  for (String carModel : carMakes) {
   String color = carColors.get(carModel);
   System.out.println("Car make:" + carModel + " Car color:" + color);
  }
  return "index";
 }

}


What I have shown is a known way of processing forms completely on the server side without aid of javascript. Though many of you know this technique, I am sure there are some out there, like my colleague, who are not aware of this; hence this post.

That's all there is to it. Happy coding :)

Monday, April 25, 2016

Preventing caching of static resources in Spring MVC legacy projects using XML config


Update (11 Sep 2018)

For Spring Boot 2, an updated version is available here

Do read this post first to understand how Spring handles resource url encoding and then proceed to the updated version.


Resource caching

Browsers typically cache static resources like javascript, css and image files. This is good as your app loads faster after the initial download of such resources. The next time around, if the resources have not changed, then the browser just fetches these from the cache. 

Problems arise when you release the next version of your web application and the javascript, css or image files have changed. The browser will tend to get the files from it's own cache.

Well you know all of this, but I am just stating it here.


Preventing browser caching

Typical web applications, built using different technologies like PHP, Python, Java Servlets, etc usually employ a simplistic approach of including a query parameter after the javascript file. This version is then changed for every release.

Example:
<script href="/resources/scripts/myjscript.js?v=1.1"></script>

But this is mainly a manual process, though smart usage of build properties and build scripts can automate this to quite an extent. Spring does this versioning automatically for you and you need not make any special changes to your maven/gradle build scripts nor perform manual update of version numbers in any properties file.

Spring 4.1 offers an effective yet simple to setup way 

Spring can create fingerprints of your resource - javascript, css and image - urls. By fingerprints, it means that your url will have a MD5 hash in it. This hash is created by MD5 one-way hashing of the contents of your resource.

Example:
/resources/scripts/cachebuster2-36ed9000c1c01d0f17d42b18c814dcdb.js

All this is possible due to the ResourceUrlEncodingFilter and any one of the version resolvers like VersionResourceResolver introduced in Spring 4.1. This official Spring blog post will give you a clear idea. You should also read the Spring documentation on static content serving and the new facilities you get.

Why bother with XML config?

Since Spring Boot has caught on rapidly, all new projects, examples and tutorials use the Java config way of configuring the application. But in an organisation there are legacy applications which were built using XML config. You do not have the luxury of updating the application by using Java Config.

I have seen applications which use Spring 4.x and XML configuration though Java config has existed for some time before Spring 4. Sometimes, developers are used to the XML config and feel more comfortable using it. The reasons maybe many, but XML configs exist and will continue to for some time in the future.

Note:
For those who want to do this using Java config, I have included a link to a very good tutorial on how to achieve this using Java config at the end of this blog post.

What you need to do to bust that browser cache

So, without further ado, this is what you need to do
  1. Modify your web.xml to register a ResourceUrlEncodingFilter
  2. Configure any of the provided ResourceResolvers
  3. Use the Spring tag or the JSTL tag to inject URLs of your static resources in your JSP page

Step 1: Modify the web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <!-- The definition of the Root Spring Container shared by all Servlets 
  and Filters -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/root-context.xml</param-value>
 </context-param>

 <!-- Creates the Spring Container shared by all Servlets and Filters -->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
         
 <!-- Add the ResourceUrlEncodingFilter -->
<filter> <filter-name>resourceUrlEncodingFilter</filter-name> <filter-class>org.springframework.web.servlet.resource.ResourceUrlEncodingFilter</filter-class> </filter> <!-- Ensure that you map it to the DispatcherServlet --> <filter-mapping> <filter-name>resourceUrlEncodingFilter</filter-name> <servlet-name>appServlet</servlet-name> </filter-mapping> <!-- Processes application requests --> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

Step 2: Configure the ResourceResolver in the application context xml file


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

 <!-- DispatcherServlet Context: defines this servlet's request-processing 
  infrastructure -->

 <!-- Enables the Spring MVC @Controller programming model -->
 <mvc:annotation-driven />
        
 <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the 
                ${webappRoot}/resources directory. The resolvers help in adding either a version number or a 
                hash to the javascript, css, image urls to prevent browser caching -->
<mvc:resources mapping="/resources/**" location="/resources/"> <mvc:resource-chain resource-cache="true" auto-registration="true"> <mvc:resolvers> <mvc:version-resolver> <mvc:content-version-strategy patterns="/**" /> <!-- <mvc:fixed-version-strategy version="1.1.1" patterns="/**"/> --> </mvc:version-resolver> </mvc:resolvers> </mvc:resource-chain> </mvc:resources> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean> <context:component-scan base-package="com.blogspot.sunitkatkar" /> </beans:beans>

Step 3: Use the JSTL or Spring tags for URLs in your JSP pages

<!DOCTYPE html>
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<spring:url value="/resources/scripts/cachebuster2.js" var="cachebusterjs" />
<c:url value="/resources/styles/cachebuster2.css" var="cachebustercss" />
<html> <head> <title>Spring MVC Resource Versioning</title> <script src="https://code.jquery.com/jquery-2.2.3.min.js" integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo=" crossorigin="anonymous"></script> <script src="${cachebusterjs}"></script> <link href="${cachebustercss}" rel="stylesheet" /> </head> <body> <h1>Hello versioned static resources world!</h1> <h4>The time on the server is ${serverTime}.</h4> <p>JS url: ${cachebusterjs}</p> <p>CSS url: ${cachebustercss}</p> <p> <button id="btnWelcome">Click Me</button> </p> </body> </html>

Results

If you run the project, you should see the main screen as below:
Fingerprint URLs

Resources


  • The above code has been uploaded as an example project "cachebuster2" on github.
  • If you want to do this using Java config, this is a good example.