Spring MVC Tutorial
This tutorial is about a simple sample project utilizing Spring 4 MVC framework to build a maven style web application based on java 7. It demonstrates some new features Spring 4 has brought up, including migrating from web.xml configuration to pure servlet 3 java based configuration, environment profiles, and jpa2 supports.
Prerequisites
Create Project
Project Dependency
Model and Database
Application Configuration
Persistence Config
Mvc Config
Environment Profiles
Web App Initializer (Replace web.xml)
Persistence Layer
Service Layer
Controller Layer
View Layer
Run
Credit
Source Code
Prerequisites
Java 7
Maven 3.2.3
Intellij Idea 14 or Eclipse
Create Project
Maven is able to create maven standard web project, run command:
mvn archetype:generate -DgroupId=edu.osumc.bmi.ird
-DartifactId=SpringMvcTutorial
-DarchetypeArtifactId=maven-archetype-webapp
-DinteractiveMode=false
Specifing webapp as the archetype will make sure the project is web application instead of a standard java project. The project layout will be created as below:
I’ve already import my project into Intellij Idea. The imporing process is pretty intuitive and straightforward, import as a external maven project and intelli will do all others for you.
Project Dependency
The very first thing for a maven project is to add all dependencies to the project after the project is created, I’m starting with my pom.xml file.
<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>edu.osumc.bmi.ird</groupId>
<artifactId>SpringMvcTutorial</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>SpringMvcTutorial Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<java-version>1.7</java-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<codehaus.jackson.version>1.9.13</codehaus.jackson.version>
<hibernate.validator.version>4.2.0.Final</hibernate.validator.version>
<hibernate.entitymanager.version>4.2.6.Final</hibernate.entitymanager.version>
<org.springframework-version>4.1.5.RELEASE</org.springframework-version>
<spring-data-jpa.version>1.6.0.RELEASE</spring-data-jpa.version>
<org.slf4j-version>1.6.6</org.slf4j-version>
<xml.jackson.version>2.5.1</xml.jackson.version>
<hsqldb.vesion>2.3.0</hsqldb.vesion>
<javax.servlet.version>3.1.0</javax.servlet.version>
<common.fileupload.version>1.3</common.fileupload.version>
<jstl.version>1.2</jstl.version>
<jsp.version>2.2</jsp.version>
<commons-lang.version>3.0</commons-lang.version>
<log4j.version>1.2.17</log4j.version>
<junit.version>4.11</junit.version>
<maven-compiler.version>3.1</maven-compiler.version>
<maven-jetty.version>9.0.6.v20130930</maven-jetty.version>
</properties>
<dependencies>
<!– spring context –>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!– spring core –>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!– spring mvc –>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!– spring jpa2 implementation –>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
<!–
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
–>
</dependency>
<!– Hibernate –>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate.validator.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.entitymanager.version}</version>
</dependency>
<!– hsql in memory db –>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.vesion}</version>
</dependency>
<!– jackson to handle json for spring –>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${codehaus.jackson.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${codehaus.jackson.version}</version>
</dependency>
<!– xml and json support –>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${xml.jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${xml.jackson.version}</version>
</dependency>
<!– common fileupload –>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${common.fileupload.version}</version>
</dependency>
<!– apache common utils –>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang.version}</version>
</dependency>
<!– logging –>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<scope>runtime</scope>
</dependency>
<!– Servlet –>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
<scope>provided</scope>
</dependency>
<!– test –>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>SpringMvcTutorial</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler.version}</version>
<configuration>
<source>${java-version}</source>
<target>${java-version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<!– Jetty –>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${maven-jetty.version}</version>
<configuration>
<httpConnector>
<port>8080</port>
<host>localhost</host>
</httpConnector>
<scanIntervalSeconds>10</scanIntervalSeconds>
</configuration>
</plugin>
</plugins>
</build>
</project>
All dependencies are defined intuitively and easy to understand, the spring framework version is 4.1.5 GA and hibernate is 4.2.6 Final. For the easy testing I’ve setup to use in-memory hsqldb, as well as jetty plugin for maven. Since we are going with non web.xml java configuration we must specify <failOnMissingWebXml>false</failOnMissingWebXml>
to make maven ignore missing web.xml file.
Model and Database
For the simple tutorial I’ve only created 2 domains for the project, User and Authority. Besides all simple attributes user and authority have a many-to-many unidirectional relationship owned by user.
Schema file for hsqldb is:
create table authorities (
id int GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) primary key,
name varchar(50));
create table users (
id int GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) primary key,
first_name varchar(50),
family_name varchar(50),
e_mail varchar(50),
phone varchar(50),
language char(2),
id_picture int,
login varchar(50) NOT NULL UNIQUE,
password varchar(50),
birth_date Date,
enabled boolean);
create table users_authorities (
id int GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1) primary key,
id_user BIGINT,
id_authority BIGINT);
Application Configuration
Persistence Config
This configuration class defines all persistence beans including data source, entity manager, transaction manager and others. For this project I’ve configured difference profiles based on the environments.
@Configuration
@EnableTransactionManagement
@ComponentScan(“edu.osumc.bmi.ird.spring.tutorial.persistence”)
@Profile(“dev”)
public class PersistenceConfigDev {
@Bean(name = “dataSource”)
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).setName(“myDb”)
.addScript(“classpath:schema.sql”).addScript(“classpath:data.sql”).build();
}
@Bean(name = “entityManagerFactory”)
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean factoryBean = new
LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan(“edu.osumc.bmi.ird.spring.tutorial.persistence.entity”);
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
factoryBean.setJpaVendorAdapter(vendorAdapter);
return factoryBean;
}
@Bean(name = “transactionManager”)
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
return transactionManager;
}
@Bean(name = “exceptionTranslation”)
public PersistenceExceptionTranslationPostProcessor exceptionTranslationPostProcessor() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
All dao classes and entities are in the package of persistence, we make persistence scan the specific package for entities dao beans.
Mvc Config
Spring mvc configuration class defines all mvc related beans. This should be consistent across all profiles so it’s not profile specific.
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = {“edu.osumc.bmi.ird.spring.tutorial.web.controller”})
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(“/resources/**”).addResourceLocations(“/resources/”);
registry.addResourceHandler(“/assets/**”).
addResourceLocations(“/WEB-INF/assets/”);
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean(name = “jspViewResolver”)
public InternalResourceViewResolver jspViewResolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setPrefix(“/WEB-INF/views/”);
bean.setSuffix(“.jsp”);
return bean;
}
@Bean(name = “multipartResolver”)
public CommonsMultipartResolver getMultipartResolver() {
return new CommonsMultipartResolver();
}
@Bean(name = “messageSource”)
public ReloadableResourceBundleMessageSource getMessageSource() {
ReloadableResourceBundleMessageSource resource = new
ReloadableResourceBundleMessageSource();
resource.setBasename(“classpath:messages”);
resource.setDefaultEncoding(“UTF-8”);
return resource;
}
}
We add a resource handler for the assets location, we are going to put all vendor libraries in the assets folder. With this handler all libraries including bootstrap, jquery and other css/script/image would be available to the views with java stl tags. e.g.
<link rel=’stylesheet’
href='<c:url value=“/assets/vendor/bootstrap-3.3.2/css/bootstrap.min.css” />‘>
Environment Profiles
Spring supports profiles for the conditional bean initialization. You may want to have different data sources for development and production environment. Adding @Profile annotation to specific configuration or bean will make those beans only available to the specific profiles. The active profile can be passed to spring by a properties file, jvm parameter and maven parameter. On this project I use a spring.properties for my active profile configuration.
/**
* Loads runtime properties for spring.
* Created by swang on 3/4/2015.
*/
public class PropertiesLoader {
public Properties load(String fileName) {
Properties prop = new Properties();
InputStream inputStream = null;
try {
inputStream = findFile(fileName);
prop.load(im);
} catch (IOException ignore) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignore) {
}
}
}
return prop;
}
/**
* Loads file from user.dir, classpath and source dir orderly.
*
* @param fileName
* @return
* @throws FileNotFoundException
*/
private InputStream findFile(String fileName) throws FileNotFoundException {
InputStream inputStream = findInWorkingDirectory(fileName);
if (inputStream == null) inputStream = findInClasspath(fileName);
if (inputStream == null) inputStream = findInSourceDirectory(fileName);
if (inputStream == null)
throw new FileNotFoundException(String.format(“File %s not found”, fileName));
return inputStream;
}
/**
* Loads file from src/main/resources/ folder of the project.
*
* @param fileName
* @return
* @throws FileNotFoundException
*/
private InputStream findInSourceDirectory(String fileName) throws FileNotFoundException {
return new FileInputStream(“src/main/resources/” + fileName);
}
/**
* Loads file from java thread classpath.
*
* @param fileName
* @return
*/
private InputStream findInClasspath(String fileName) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
}
/**
* Loads file from user.dir system environment variable.
*
* @param fileName
* @return
*/
private InputStream findInWorkingDirectory(String fileName) {
try {
return new FileInputStream(System.getProperty(“user.dir”) + fileName);
} catch (FileNotFoundException e) {
return null;
}
}
}
Web App Initializer (Replace web.xml)
The class is replacing web.xml, it initiates spring context and spring dispatcher.
public class WebAppInitializer implements WebApplicationInitializer {
private static final PropertiesLoader propertiesLoader = new PropertiesLoader();
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Create the ‘root’ Spring application context
AnnotationConfigWebApplicationContext context = getContext();
// Manage the lifecycle of the root application context
servletContext.addListener(new ContextLoaderListener(context));
// Create the dispatcher servlet’s Spring application context
AnnotationConfigWebApplicationContext dispatcherServlet = new
AnnotationConfigWebApplicationContext();
dispatcherServlet.register(MvcConfig.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(“dispatcher”, new
DispatcherServlet(dispatcherServlet));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(“/”);
}
/**
* Creates annotation driven context initializer. Active profile will be loaded from
* properties files.
*
* @return AnnotationConfigWebApplicationContext
*/
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation(“edu.osumc.bmi.ird.spring.tutorial.config”);
//context.register(AppConfig.class, ServiceConfig.class);
Properties properties = propertiesLoader.load(ResourceProperties.SPRING_PROPERTIES_FILE);
context.getEnvironment().setActiveProfiles(
properties.getProperty(“spring.profiles.active”));
return context;
}
}
Here we load active profile from spring.properties file if existing, and then set configuration based on the active profile.
Persistence Layer
To avoid putting hibernate/sql sensitive code into the dao layer, I’ve utilized jpa and generics into my GenericDao implementation class. (I’ve not used spring jpa repository, but you can if you want.)
public abstract class GenericDaoImpl<T, PK extends Serializable> implements GenericDao<T, PK> {
private static final Logger LOG = LoggerFactory.getLogger(GenericDaoImpl.class);
@PersistenceContext
private EntityManager entityManager;
private Class<T> type;
@SuppressWarnings(“unchecked”)
public GenericDaoImpl() {
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
type = (Class<T>) pt.getActualTypeArguments()[0];
LOG.debug(“Parameterized generic type is “ + type.getName());
}
@Override
public T create(T persistentObject) {
entityManager.persist(persistentObject);
return persistentObject;
}
@Override
public List<T> getAll() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> query = builder.createQuery(type);
Root<T> root = query.from(type);
query.select(root);
TypedQuery<T> typedQuery = entityManager.createQuery(query);
LOG.debug(“getAll query is “ + query.toString());
return typedQuery.getResultList();
}
@Override
public T get(PK id) {
return entityManager.find(type, id);
}
@Override
public T update(T persistentObject) {
return entityManager.merge(persistentObject);
}
@Override
public void delete(PK id) {
entityManager.remove(entityManager.getReference(type, id));
}
/**
* Count record number using JPA criteria builder.
*
* @param typedQuery the criteria with query root, where clause
* @return count of the result
*/
@Override
public Long countAllByCriteria(TypedQuery<Long> typedQuery) {
return typedQuery.getSingleResult();
}
/**
* Find all records using JPA criteria builder.
*
* @param typedQuery the criteria with query root, where clause and order by
* @return List of the result objects
*/
@Override
public List<T> findAllByCriteria(TypedQuery<T> typedQuery) {
return typedQuery.getResultList();
}
}
This class is made abstract intentionally to make sure it can only be subclassed but not initiated. All other Daos just extend this GenericDaoImpl will make the basic jpa based crud operations available for the service layer.
Service Layer
For the demo purpose the service layer doesn’t have any special logic but acts as a delegator to pass data access call from controller to dao layer. Transactional annotation is specified on service layer.
Controller Layer
Controller is quite simple by retrieving the result and rendering the view with the model. I also have a rest call to return the result in json array. The below snippet is from UserController:
/**
* rest web service
*
* @return
*/
@RequestMapping(value = “/usersList”, method = RequestMethod.GET)
@ResponseBody
public List<User> usersList() {
logger.debug(“get json user list”);
return userService.getAll();
}
/**
* return ModelAndView with users
*
* @return
*/
@RequestMapping(value = “users”, method = RequestMethod.GET)
public ModelAndView getUsers() {
logger.debug(“display user list”);
ModelAndView mv = new ModelAndView(“usersView”);
mv.addObject(“usersModel”, userService.getAll());
return mv;
}
View Layer
Simple user view is as below:
<%@ page language=“java” contentType=“text/html; charset=UTF-8” pageEncoding=“UTF-8” %>
<%@ taglib prefix=“spring” uri=“http://www.springframework.org/tags” %>
<%@ taglib uri=“http://java.sun.com/jsp/jstl/core” prefix=“c” %>
<html>
<head>
<link rel=’stylesheet’ href='<c:url
value=“/assets/vendor/bootstrap-3.3.2/css/bootstrap.min.css” />‘>
<title>User Table</title>
</head>
<body>
<div class=“col-sm-offset-1 col-sm-10”>
<legend>
<spring:message code=“table.user.title”/>
</legend>
<div>
<table id=“dataTable” class=“table table-striped table-bordered”>
<thead>
<tr>
<th><spring:message code=“table.user.id”/></th>
<th><spring:message code=“table.user.firstName”/></th>
<th><spring:message code=“table.user.falilyName”/></th>
<th><spring:message code=“table.user.email”/></th>
</tr>
<thead>
<tbody>
<c:forEach var=“u” items=“${usersModel}”>
<tr>
<td>${u.id}</td>
<td>${u.firstName}</td>
<td>${u.familyName}</td>
<td>${u.email}</td>
<tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</body>
</html>
Run
Build the project: mvn clean compile
Run it: mvn jetty:run
And the project should be accessible at localhost:8080
Credit
Many thanks to many authors for lots of excellent tutorials I’ve found online. If you think any content in my post conflicts with your articles, please let me know and I will remove it.
Source Code
The source code is hosted on my github repository: Spring MVC Tutorial