Setup a simple Spring MVC site with Maven

There are already a lot of good tutorials on setting up MVC apps, and if you are starting a real project with Spring MVC I would suggest AppFuse. This guide is a basic setup which I use as a base example for other articles. It covers a few core concepts.

Maven

Project layout

The first step is to create a basic Maven project. If you are not using eclipse or IntelliJ which can generate the project for you then create the following structure.

SampleProject
->    src
-->       main
--->          java	
--->          resources
--->          webapp
-->       test
--->          java
--->          resources
-     pom.xml
  • src is the source to your project.
  • pom.xml is the build and dependency file used by Maven.
  • main & test are source folders and should mirror in child folder structure. Main is your live code, Test is you junit, selenium, mocks or whatever you need to prove the app is stable.
  • java contains the source java classes
  • resources will have property files, config files and other non java classes needed for your code
  • webapp (only under main) is were the web files css, js, jsp, servlet.xml or web.xml, WEB-INF and all other web display assets.

Setup a basic pom

Below is the pom for this sample application. Detailed information on the sections are available on the Maven site.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>SampleMVC</groupId>
    <artifactId>SampleMVC</artifactId>
    <version>1.0</version>
    <packaging>war</packaging>

    <properties>
        <!-- Build Properties -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- Dependency Versions -->
        <spring.version>3.2.1.RELEASE</spring.version>
        <jstl.version>1.2</jstl.version>
        <servlet.version>2.5</servlet.version>
        <bootstrap.version>2.2.2</bootstrap.version>
        <jquery.version>1.9.0</jquery.version>
        <sitemesh.version>2.5-atlassian-5</sitemesh.version>
        <commons-lang.version>2.6</commons-lang.version>
    </properties>

    <build>
    </build>

    <dependencies>

        <!-- Servlet & JSTL -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>${servlet.version}</version>
        </dependency>

        <!-- Display template & layout -->
        <dependency>
            <groupId>opensymphony</groupId>
            <artifactId>sitemesh</artifactId>
            <version>${sitemesh.version}</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>${bootstrap.version}</version>
        </dependency>

        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Apache lang utils -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>${commons-lang.version}</version>
        </dependency>
    </dependencies>

    <profiles>
        <profile>
            <id>jetty</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.mortbay.jetty</groupId>
                        <artifactId>maven-jetty-plugin</artifactId>
                        <version>6.1.26</version>
                        <configuration>
                            <useTestClasspath>true</useTestClasspath>
                            <contextPath>/app</contextPath>
                            <stopKey>1</stopKey>
                            <stopPort>9999</stopPort>
                        </configuration>
                        <executions>
                            <execution>
                                <id>start-jetty</id>
                                <phase>pre-integration-test</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <daemon>true</daemon>
                                </configuration>
                            </execution>
                            <execution>
                                <id>stop-jetty</id>
                                <phase>post-integration-test</phase>
                                <goals>
                                    <goal>stop</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
  • groupId, artifactId and version are used to identify the project for Maven.
  • Properties allows you to define values that can be used in the pom, works great to reduce repeating versions with syntax ${value}. You can also use the resource settings to populate property files (spring, application.properties…).
  • Build we don’t use here but it’s the place for plugins, build tasks and anything you need to get the app packaged just the way you want. In this case we just run jetty so nothing special is needed.
  • Dependencies are all the dependent java classes needed to run the app. There are different scopes allowed that tell Maven when to load them. Typically compile (the default) is fine, the next most common I use is test. Maven will load the dependency only for testing and exclude it from the deployment package. Another thing to note is transitive dependencies. Maven 2.0 will automatically pull in dependencies of the declared project dependencies to save you from having to find them all on you own. Maven is fairly good about resolving conflicts in versions but it may be necessary to “exclude” certain artifacts from declared dependencies. If that gets messy then checkout the dependency management which can set default versions and override transitive dependencies. It also useful in parent poms or multi-module structures (later topic).
      Dependency overview for this project

    • jstl & servlet-api – These are used for the servlet and jsp pages. This is not specific to MVC but required for a webapp.
    • sitemesh – Template engine for the site UI.
    • bootstrap – Twitter bootstrap that also includes jQuery. Both help to make quick layouts. This is provided by WebJars, which allows us to treat the boilerplate layout code as a jar dependency and keeps the webapp clean. There are a lot of other packages available for extending the UI.
    • spring-core – name says it all.
    • spring-beans – bean definitions.
    • spring-context – bean registration (component scan, annotation config…)
    • spring-web – base web properties, RequestMapping…
    • spring-webmvc – MVC components, Controllers, DefaultAnnotationHandlerMapping and AnnotationMethodHandlerAdapter
    • commons-lang – Using this for StringUtils
  • Profile is the last part for this file. Profiles can be enabled by default (as seen here) or loaded via the -P flag when invoking maven. This is great when using build servers where environment specific packages or special testing should to be turned on or off. However this is does affect the packaged artifact at build time. If you need runtime profiles then the Spring Environment addition in version 3.1 can help. The profile defined here is for running Jetty so we can quickly test the app locally.

Servlet and Web files

web.xml

Create web.xml and parent folders in src/main/webapp/WEB-INF/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">

    <display-name>Sample App</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            <!--classpath resource of spring configs or other required configs go here -->
        </param-value>
    </context-param>

    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
    </filter>

    <!-- Filter Mappings -->
    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

    <!-- Default Page Support -->
    <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- MVC Servlet - see sample-servlet.xml for specific config -->
    <servlet>
        <servlet-name>sample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>      
    </servlet>

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

The web.xml file instructs the servlet container (in our case it’s jetty) how to start up and what to load. This is for servlet 2.5, in servlet 3.0 this file is not necessary and annotations can be used (later topic).

  • context-param – defines resources that are used by the servlet. Typically an applicationContext.xml for Spring would be listed here and used by the ContextLoaderListener
  • filter – filters make up a filter chain so order is important. In this case we are only using one. Each filter will have a definition and a corresponding mapping (defined by same name) that tells the servlet when to apply it. This filter is used by sitemesh to build out web template.
  • listener – invoked when the application starts. This listener will look for a spring config and initialize the ApplicationContext. By convention it will look for sample-servlet.xml in the classpath where “sample” is the servlet name. You can also specify one or more in the contextConfigLocation section.
  • servlet – Spring web magic. This defines a servlet that will process all requests through Spring MVC.
  • servlet-mapping – defines the url pattern that this servlet listen on. I chose to make it root and use Spring MVC resource definitions (in sample-servlet.xml) to exclude non Spring MVC components like CSS, JS & images
  • .

sample-servlet.xml

Create the sample-servlet.xml in the same location as web.xml.

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

    <!-- Scan for spring annotated components -->
    <context:component-scan base-package="com.luckyryan.sample"/>

    <!-- Process annotations on registered beans like @Autowired... -->
    <context:annotation-config/>

    <!-- This tag registers the DefaultAnnotationHandlerMapping and
         AnnotationMethodHandlerAdapter beans that are required for Spring MVC  -->
    <mvc:annotation-driven/>

    <!-- Exception Resolver that resolves exceptions through @ExceptionHandler methods -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"/>

    <!-- View Resolver for JSPs -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- This tag allows for mapping the DispatcherServlet to "/" -->
    <mvc:default-servlet-handler/>

    <!-- resources exclusions from servlet mapping -->
    <mvc:resources mapping="/assets/**" location="classpath:/META-INF/resources/webjars/"/>
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/img/**" location="/img/"/>
    <mvc:resources mapping="/js/**" location="/js/"/>

</beans>

Sitemesh

Create the required sitemesh files. This defines our decorators & parsers. See sitemesh for more info.

<sitemesh>
    <property name="decorators-file" value="/WEB-INF/decorators.xml"/>
    <excludes file="${decorators-file}"/>
    <page-parsers>
        <parser default="true" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
        <parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
        <parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
    </page-parsers>

    <decorator-mappers>
        <mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
            <param name="config" value="${decorators-file}"/>
        </mapper>
    </decorator-mappers>
</sitemesh>
<decorators defaultdir="/decorators">
    <excludes>
        <pattern>/resources/*</pattern>
    </excludes>
    <decorator name="default" page="default.jsp">
        <pattern>/*</pattern>
    </decorator>
</decorators>

Create the decorators folder and default.jsp. (src/main/webapp/decorators/default.jsp). <decorator-title/> & <decorator-body/> will pull in the respective sections of the child page.

<%@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
    < %@ taglib prefix="decorator" uri="http://www.opensymphony.com/sitemesh/decorator" %>

    <meta charset="utf-8"/>

    <title><decorator:title></decorator:title></title>

    <link rel="stylesheet" type="text/css"
          href="${pageContext.request.contextPath}/assets/bootstrap/2.2.2/css/bootstrap.min.css" media="all"/>

    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
    <!--[if lt IE 9]>
    <script src="https://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    < ![endif]-->

    <decorator:head></decorator:head>

    <style>
        body {
            padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
        }
    </style>
</head>
<body>

<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">
        <div class="container">
            <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </a>
            <a class="brand" href="#">Sample App</a>

            <div class="nav-collapse collapse">
                <ul class="nav">
                    <li class="active"><a href="${pageContext.request.contextPath}/">Home</a></li>
                </ul>
            </div>
            <!--/.nav-collapse -->
        </div>
    </div>
</div>

<div class="container">

    <c:if test="${page_error != null }">
        <div class="alert alert-error">
            <button type="button" class="close" data-dismiss="alert">&times;</button>
            <h4>Error!</h4>
                ${page_error}
        </div>
    </c:if>

    <decorator:body></decorator:body>

    <footer>

    </footer>
</div>


<script type="text/javascript"
        src="${pageContext.request.contextPath}/assets/jquery/1.8.2/jquery.min.js"></script>
<script type="text/javascript"
        src="${pageContext.request.contextPath}/assets/bootstrap/2.2.2/js/bootstrap.min.js"></script>
</body>
</html>

JSP Pages

Create an index.jsp and show.jsp in src/main/webapp/WEB-INF/pages/*.

< %@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
< %@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
< %@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
< %@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
    <title>Sample App Sign-up Page</title>
</head>
<body>

<h1>Sign Up Here!</h1>

<form:form method="post" modelAttribute="signupForm" action="create" id="signupForm" cssClass="form-horizontal"
           autocomplete="off">
    <fieldset>
        <legend>Enter Your Information</legend>

        <c:set var="emailErrors"><form:errors path="email"></form:errors></c:set>
        <div class="control-group<c:if test="${not empty emailErrors}"> error">
            <label class="control-label" for="field-email">Email</label>

            <div class="controls">
                <form:input path="email" id="field-email" tabindex="1" maxlength="45" placeholder="Email"></form:input>
                <form:errors path="email" cssClass="help-inline" element="span"></form:errors>
            </div>
        </div>
        <c:set var="firstNameErrors"><form:errors path="firstName"></form:errors></c:set>
        <div class="control-group<c:if test="${not empty firstNameErrors}"> error">
            <label class="control-label" for="field-firstName">First Name</label>
            <div class="controls">
                <form:input path="firstName" id="field-firstName" tabindex="2" maxlength="35" placeholder="First Name"></form:input>
                <form:errors path="firstName" cssClass="help-inline" element="span"></form:errors>
            </div>
        </div>
        <c:set var="lastNameErrors"><form:errors path="lastName"></form:errors></c:set>
        <div class="control-group<c:if test="${not empty lastNameErrors}"> error">
            <label class="control-label" for="field-lastName">Last Name</label>
            <div class="controls">
                <form:input path="lastName" id="field-lastName" tabindex="3" maxlength="35" placeholder="Last Name"></form:input>
                <form:errors path="lastName" cssClass="help-inline" element="span"></form:errors>
            </div>
        </div>
        <div class="form-actions">
            <button type="submit" class="btn btn-primary">Sign up</button>
            <button type="button" class="btn">Cancel</button>
        </div>
    </fieldset>
</form:form>


</body>
</html>
< %@ page language="java" pageEncoding="UTF-8" contentType="text/html;charset=utf-8" %>
<html>
<head>
    <title>Sample App Sign-up Page</title>
</head>
<body>

<h1>Sign Up Complete!</h1>
</body>
</html>

Spring MVC

Controllers

Now we have an app with pages but nothing to actually handle the request and map to a page.

The next step is to create a Controller and register it with Spring.

Create IndexController.java in src/main/java/com/luckyryan/sample/webapp/controller/IndexController.java. NOTE: this is NOT the same webapp folder used with the jsp files, this is under the package structure of java.

package com.luckyryan.sample.webapp.controller;

import com.luckyryan.sample.model.SignupForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
public class IndexController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView index() {
        return new ModelAndView("index", "signupForm", new SignupForm());
    }

    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public String create(Model model, SignupForm signupForm, BindingResult result, RedirectAttributes redirectAttributes) {

        if (result.hasErrors()) {
            return "index";
        }

        return "show";
    
    }

    @RequestMapping(value = "/security-error", method = RequestMethod.GET)
    public String securityError(RedirectAttributes redirectAttributes) {
        redirectAttributes.addFlashAttribute("page_error", "You do have have permission to do that!");
        return "redirect:/";
    }

}

The @Controller annotation indicates that this class is a MVC controller. Spring will use the request mappings to figure out what url will hit which controller and method. Since we only have one controller using a mapping of “/” will capture root requests. If we had multiple controllers then RequestMapping could be defined for the entire class e.g. “registration” or “account” and any method on that controller would inherit the path. For this case I expect http://localhost:8080/sample/ would invoke IndexController.index(). In the example of “registration” then I would expect http://localhost:8080/sample/registration/ would invoke index.

Each method can return either a ModelAndView or just a string. The return value will use the ViewResolver to locate the proper jsp page to load. In our case we had

<!-- View Resolver for JSPs -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/pages/"/>
    <property name="suffix" value=".jsp"/>
</bean>

This means that a method return of “index” would resolve to /WEB-INF/pages/index.jsp. In the case of jsp, json and other views, View resolvers can be chained and ordered until a desired view is found. Acceptable return values are set by the ViewResolver. For this method we only want to show a blank for, to do that we instantiate a new SignupForm object (to be created next) and assign it to “signupForm” in the model. This allows use to access that object in the jsp via jstl or spring tags. Spring tags make it easy and do the work for you.

The create method only returns a string. Since model is available as a parameter we can directly access or set necessary values. The method signatures for controller request method can vary greatly based on what you need, just pay attention to order as some parameters do better when left as the last one.

There is a lot of opportunity for configuration here. Spring has excellent documentation when wanting to go beyond the basic.

Form Model

Create the SignupForm.java class src/main/java/com/luckyryan/sample/model/SignupForm.java

package com.luckyryan.sample.model;

public class SignupForm {

    private String firstName;
    private String lastName;
    private String email;

    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 getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Spring can bind models to the request automatically so we don’t need to mess with parameter mapping. Enhancements such as validation are also possible and make life easy.

Now we can start up the servlet container and app from Maven with the following command: mvn clean jetty:run

The app should start up and be accessible at http://localhost:8080/sample/

The simple form will display, enter any values and we get the confirmation page.

signup_page

signup_complete

This covers the basics of the app. Other topics will cover where to go from here and handle common tasks when building apps.

11 Comments

  1. Rafael January 16, 2015
  2. Vibhu July 6, 2014
    • Ryan July 13, 2014
  3. P March 26, 2014
    • Ryan April 5, 2014
  4. kulthana November 24, 2013
    • Ryan November 24, 2013
  5. John November 12, 2013
    • Ryan November 24, 2013
  6. Phuong August 13, 2013
    • Ryan August 13, 2013

Leave a Reply

Your email address will not be published. Required fields are marked *