Building Spring MVC with Maven

This article assumes that your workspace is C:\foo.

Generating archetype

C:\ Command Prompt
C:\foo>mvn archetype:generate -Dfilter=maven-archetype-webapp
Choose archetype:
1: remote -> org.apache.maven.archetypes:maven-archetype-webapp 
(An archetype which contains a sample Maven Webapp project.)
Choose a number or apply filter 
(format: [groupId:]artifactId, case sensitive contains): : 1
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
Choose a number: 5: ↵
Define value for property 'groupId': : net.java_school
Define value for property 'artifactId': : spring-bbs
Define value for property 'version':  1.0-SNAPSHOT: : ↵
Define value for property 'package':  net.java_school: : ↵
Confirm properties configuration:
groupId: net.java_school
artifactId: spring-bbs
version: 1.0-SNAPSHOT
package: net.java_school
 Y: : ↵

Maven creates the spring-bbs folder in C:\foo through the build process. C:\foo\spring-bbs is the root directory of your project. The src/main/webapp folder, just above WEB-INF, is the document base.

Create a Tomcat context file as follows.

spring-bbs.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context
  docBase="C:/foo/spring-bbs/src/main/webapp"
  reloadable="true">
</Context>

Copy the spring-bbs.xml file to the CATALINA_HOME/conf/Catalina/localhost folder. After restart Tomcat, visit http://localhost:8080/spring-bbs to see the web application is working.

Spring MVC Test

Modify pom.xml like below.

pom.xml
<?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/maven-v4_0_0.xsd">
    
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.java_school</groupId>
  <artifactId>spring-bbs</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>spring-bbs Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <properties>
    <spring.version>5.3.9</spring.version>
    <jdk.version>11</jdk.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>    
    <!-- Oracle JDBC Driver -->
    <dependency>
      <groupId>com.oracle</groupId>
      <artifactId>ojdbc6</artifactId>
      <version>11.2.0.2.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.4</version>
    </dependency>
    <!-- Logging -->
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jcl -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-jcl</artifactId>
        <version>2.14.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.11.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>    
    <!-- Servlet JSP JSTL -->
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
      <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/jstl/jstl -->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>spring-bbs</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.1</version>
          <configuration>
            <source>${jdk.version}</source>
            <target>${jdk.version}</target>
            <compilerArgument></compilerArgument>
            <encoding>UTF-8</encoding>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
          <configuration>
            <filesets>
              <fileset>
                <directory>src/main/webapp/WEB-INF/classes</directory>
              </fileset>
              <fileset>
                <directory>src/main/webapp/WEB-INF/lib</directory>
              </fileset>
            </filesets>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

The Maven repository does not provide Oracle JDBC. Therefore, you need to install ojdbc6.jar to your local repository.

Copy the ojbdc6.jar file from the Oracle installation directory to the desired location, and then execute the following command in that location.

mvn install:install-file -Dfile=ojdbc6.jar -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.2.0 -Dpackaging=jar

You can download ojdbc6.jar from http://www.oracle.com/technetwork/apps-tech/jdbc-112010-090769.html.

Modify web.xml like below.

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1"
  metadata-complete="true">
    
  <display-name>Spring BBS</display-name>
  
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
      </init-param>
      <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
        
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
    
  <servlet>
    <servlet-name>spring-bbs</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
    
  <servlet-mapping>
    <servlet-name>spring-bbs</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  
  <error-page>
    <error-code>404</error-code>
    <location>/WEB-INF/views/400.jsp</location>
  </error-page>

  <error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/views/500.jsp</location>
  </error-page> 
 
</web-app>

Of the filters above, the one that executes the setCharacterEncoding("UTF-8") code on every request is essential for non-English websites. You should declare it before any other filters.

Since we declared Spring MVC's DispatcherServlet with the name spring-bbs in web.xml, we need to create a spring configuration file named spring-bbs-servlet.xml.

Create a spring-bbs-servlet.xml file in the WEB-INF folder like below.

spring-bbs-servlet.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
  xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  
</beans>

Create the following classes for testing.

Player.java
package net.java_school.soccer;

public class Player {
  private String id;
  private String passwd;
  private String name;
  
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getPasswd() {
    return passwd;
  }
  public void setPasswd(String passwd) {
    this.passwd = passwd;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}
PlayerDao.java
package net.java_school.soccer;

public class PlayerDao {

  public Player selectOne(String id) {
    Player player = new Player();
    if (id != null && id.equals("1")) {
      player.setName("Lionel Messi");
    } else if (id != null && id.equals("2")) {
      player.setName("Cristiano Ronaldo");
    } else {
      player.setName("Neymar");
    }
    return player;
  }
}
PlayerService.java
package net.java_school.soccer;

public class PlayerService {
  private PlayerDao dao;
  
  public void setDao(PlayerDao dao) {
    this.dao = dao;
  }

  public Player getPlayer(String id) {
    return dao.selectOne(id);
  }
}
PlayerController.java
package net.java_school.soccer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class PlayerController implements Controller {
  
  private PlayerService service;
  
  public void setService(PlayerService service) {
    this.service = service;
  }
  
  public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse res) throws Exception {
    String id = req.getParameter("id");
    Player player = service.getPlayer(id);
    
    Map<String, Object> model = new HashMap<String, Object>();
    model.put("player", player);
    
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/WEB-INF/views/player/test.jsp");
    modelAndView.addAllObjects(model);
    
    return modelAndView;
  }
}

Create test.jsp like below.

/src/main/webapp/WEB-INF/views/player/test.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>TEST</title>
</head>
<body>
${player.name }
</body>
</html>

Build:

C:\ Command Prompt
C:\foo\sprng-bbs>mvn clean compile war:inplace

Test:

Restart Tomcat and visit http://localhost:8080/spring-bbs/player/test?id=1.

SimpleUrlHandlerMapping

Let's change the handler mapping to use SimpleUrlHandlerMapping. Modify spring-bbs-servlet.xml like below.

spring-bbs-servlet.xml
<!-- omit -->

<!-- HandlerMapping -->
<bean id="handlerMapping" 
    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  
  <property name="mappings">
    <value>/player/test=playerController</value>
  </property>
  
</bean>

<bean id="playerController" 
    class="net.java_school.soccer.PlayerController"
    p:service-ref="playerService" />
  
<!-- omit -->

You need to remove the name attribute of the playerController element.

<bean id="playerController" name="/player/test"

Test:

Restart Tomcat and visit http://localhost:8080/spring-bbs/player/test?id=1.

InternalResourceViewResolver

InternalResourceViewResolver is an intuitive and easy-to-understand view resolver. Let's change the view resolver. Open the spring-bbs-servlet.xml file and add the following to the <!-- ViewResolver --> section.

spring-bbs-servlet.xml
<!-- ViewResolver -->
<bean id="internalResourceViewResolver" 
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass">
    <value>org.springframework.web.servlet.view.JstlView</value>
  </property>
  <property name="prefix">
    <value>/WEB-INF/views/</value>
  </property>
  <property name="suffix">
    <value>.jsp</value>
  </property>
</bean>

Modify PlayerController.java like below.

PlayerController.java
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("player/test");
modelAndView.addAllObjects(model);

return modelAndView;

Now the InternalResourceViewResolver would interpret "player/test" as "/WEB-INF/views/player/test.jsp".

Since the Java source has changed, rebuild it.

Restart Tomcat and visit http://localhost:8080/spring-bbs/player/test?id=1.

RequestMappingHandlerMapping

SimpleUrlHandlerMapping requires one controller per request. With RequestMappingHandlerMapping, you can place a controller by use case. RequestMappingHandlerMapping uses annotations and auto-scans to map handlers.

Open the spring-bbs-servlet.xml and remove the >! - HandlerMapping - >.

Modify spring-bbs-servlet.xml like below.

spring-bbs-servlet.xml
<?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"
  xmlns:p="http://www.springframework.org/schema/p"
  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">
  
  <mvc:annotation-driven />
    
  <context:component-scan
      base-package="net.java_school.soccer" />
  
  <!-- ViewResolver -->
  <bean id="internalResourceViewResolver" 
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass">
      <value>org.springframework.web.servlet.view.JstlView</value>
    </property>
    <property name="prefix">
      <value>/WEB-INF/views/</value>
    </property>
    <property name="suffix">
      <value>.jsp</value>
    </property>
  </bean>
  
  <bean id="playerService" class="net.java_school.soccer.PlayerService">
    <property name="dao" ref="playerDao" />
  </bean>
  
  <bean id="playerDao" class="net.java_school.soccer.PlayerDao" />
  
</beans>

<context:component-scan base-package="net.java_school.soccer" /> allows Spring to find and register annotated components. (Here, components mean Dao, Service, Controller) With this setting, you can omit specific handler mapping settings from the Spring configuration file. <mvc: annotation-driven /> makes all the annotation-based features available. So, <context:component-scan /> does not work without <mvc:annotation-driven / >. You need to annotate the controller to let Spring find the controller.

Modify PlayerController.java like below.

PlayerController.java
package net.java_school.soccer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/player")
public class PlayerController {
  
  @Autowired
  private PlayerService service;
  
  @RequestMapping(value="/test", method={RequestMethod.GET, RequestMethod.POST})
  public String pickPlayer(String id, Model model) {
    Player player = service.getPlayer(id);
    model.addAttribute("player", player);
    return "player/test";
  }
}

After Bulid, restart Tomcat and visit http://localhost:8080/spring-bbs/player/test?id=1.

Annotations in PlayerController.java
@Controller in the class declaration indicates that the class is a controller component.
@RequestMapping("/player") in the class declaration says that the controller is responsible for all requests involving "/player".
@RequestMapping(value="/test",method={RequestMethod.GET,RequestMethod.POST}) in the method declaration indicates that the method is responsible for the "/player/test" request of GET or POST method.
@Autowired in the variable declaration tells Spring to create a dependent object and assign its reference to this variable. This process works even if the variable's accessor is private.

Since the component scan is working, you can reduce the configuration of spring-bbs-servlet.xml by adding annotations to the service and Dao.

PlayerDao.java
package net.java_school.soccer;

import org.springframework.stereotype.Repository;

@Repository
public class PlayerDao {

  public Player selectOne(String id) {
    Player player = new Player();
    if (id != null && id.equals("1")) {
      player.setName("Lionel Messi");
    } else if (id != null && id.equals("2")) {
      player.setName("Cristiano Ronaldo");
    } else {
      player.setName("Neymar");
    }
    return player;
  }
}
PlayerService.java
package net.java_school.soccer;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class PlayerService {
  @Autowired
  private PlayerDao dao;
  
  public Player getPlayer(String id) {
    return dao.selectOne(id);
  }
}

The Spring configuration file is simplified as follows:

spring-bbs-servlet.xml
<?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"
  xmlns:p="http://www.springframework.org/schema/p"
  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">

  <mvc:annotation-driven />
    
  <context:component-scan
    base-package="net.java_school.soccer" />
    
  <!-- ViewResolver -->
  <bean id="internalResourceViewResolver" 
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass">
      <value>org.springframework.web.servlet.view.JstlView</value>
    </property>
    <property name="prefix">
      <value>/WEB-INF/views/</value>
    </property>
    <property name="suffix">
      <value>.jsp</value>
    </property>
  </bean>
  
</beans>

Set up the project in eclipse

Start Eclipse and select your workspace as C:\foo. In the Project Explorer view, use the right mouse button to display context menus. Import the spring-bbs project into Eclipse.

Context menu Import

Maven Project Import

If the pom.xml file changes, you need to synchronize pom.xml with Eclipse.

eclipse and pom.xml synchronized

References