Sunday, December 2, 2012

Spring and Thymeleaf with JavaConfig (Part 1)

In this tutorial, we will create a CRUD application based on Spring MVC 3.x and Spring Data JPA. We will utilize JavaConfig instead of XML to configure our application. For the view layer, we will use Thymeleaf as our template engine instead of JSP to process our html pages.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Creating the View
    • HTML Mockup
    • Thymeleaf Integration
  3. JavaConfig
    • ApplicationContext.java
    • SpringDataConfig.java
    • ThymeleafConfig.java
    • ApplicationInitializer.java
  4. Layers
    • Domain
    • Service
    • Controller
  5. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Dependencies

These are the main Maven dependencies:
  • Spring 3.2.0.RC1
  • Spring Data JPA 1.2.0.RELEASE
  • Thymeleaf 2.0.14
  • Hibernate 3.6.3.Final
  • See pom.xml for full details

Required Tools

These are the minimum required tools:
  • Git
  • Maven 3.0.4
  • MySQL
  • Eclipse IDE or SpringSource Tool Suite (STS)

GitHub Repository

There are two versions of the application: a JavaConfig-based and an XML config-based app. Both versions are identical in their feature set.

Functional Specs


Our application's requirements are quite straightforward:
  • Create a simple form to manage user information
  • Provide the following fields: first name, last name, username, role
  • Username must be unique
  • Provide CRUD operations
  • Provide table to view all users

Here's our Use Case diagram:


[User]-(Add)
[User]-(View)
[User]-(Update)
[User]-(Delete)

//http://yuml.me/

Here's a screenshot of our working application:


Next

In the next section, we will focus on the view layer. We'll start writing the HTML mockup template; then we'll integrate it with Thymeleaf. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring and Thymeleaf with JavaConfig (Part 1) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring and Thymeleaf with JavaConfig (Part 2)

Review


In the previous section, we have read the functional specs of the application. In this section, we will focus on the view layer, create an HTML mockup, and integrate our mockup with Thymeleaf.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Creating the View
    • HTML Mockup
    • Thymeleaf Integration
  3. JavaConfig
    • ApplicationContext.java
    • SpringDataConfig.java
    • ThymeleafConfig.java
    • ApplicationInitializer.java
  4. Layers
    • Domain
    • Service
    • Controller
  5. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Creating the View


In designing our application we'll start with the view layer because we can. Thanks to Thymeleaf creating HTML mockups is easy. Thymeleaf allows us to use these mockups as our HTML templates without any aesthetic changes. In addition, it passes W3C Markup Validation Service with flying colors.

What is Thymeleaf?

Thymeleaf is a Java library. It is an XML / XHTML / HTML5 template engine (extensible to other formats) that can work both in web and non-web environments. It is better suited for serving XHTML/HTML5 at the view layer of web applications, but it can process any XML file even in offline environments.

It provides an optional module for integration with Spring MVC, so that you can use it as a complete substitute of JSP in your applications made with this technology, even with HTML5.

The main goal of Thymeleaf is to provide an elegant and well-formed way of creating templates. Its Standard and SpringStandard dialects allow you to create powerful natural templates, that can be correctly displayed by browsers and therefore work also as static prototypes. You can also extend Thymeleaf by developing your own dialects.

Source: Thymeleaf.org

HTML Mockup


Let's create our HTML mockup. You can see the final mockup below:


First, we create a new HTML page. Note that this is a very simple HTML document that validates with W3C Markup Validation Service.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../resources/css/style.css"/>
<title>Title</title>
</head>
<body>
<div>
<table class="box-table-a">
<caption>Site Users</caption>
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">First Name</th>
<th scope="col">Last Name</th>
<th scope="col">Username</th>
<th scope="col">Role</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>George</td>
<td>Washington</td>
<td>gwash</td>
<td>admin</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr>
<td>2</td>
<td>John</td>
<td>Adams</td>
<td>jadam</td>
<td>user</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr>
<td>3</td>
<td>Thomas</td>
<td>Jefferson</td>
<td>tjeff</td>
<td>user</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr>
<td>4</td>
<td>James</td>
<td>Madison</td>
<td>jmadi</td>
<td>admin</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr>
<td>5</td>
<td>James</td>
<td>Monroe</td>
<td>jmonr</td>
<td>admin</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
</tbody>
</table>
<form action="#" method="post">
<table class="box-table-a">
<caption>New User</caption>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Username</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" hidden="hidden"/>
<input type="text"/></td>
<td><input type="text"/></td>
<td><input type="text"/></td>
<td><select>
<option value="1">Access Type 1</option>
<option value="2">Access Type 2</option>
</select></td>
</tr>
<tr>
<td>
<button type="submit">Action</button>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</form>
</div>
</body>
</html>
view raw users.html hosted with ❤ by GitHub


Next, we create an external CSS file. Because I'm not really a designer, I have scoured the web for a simple but elegant table style. I found one from Top 10 CSS Table Designs.

body {
font-family: Arial, sans-serif;
font-size: 1em;
}
caption {
font-size: 18px;
}
/**
* Table style from Top 10 CSS Table Designs
* http://coding.smashingmagazine.com/2008/08/13/top-10-css-table-designs/
*/
.box-table-a {
font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
font-size: 12px;
margin: 45px;
width: 700px;
text-align: left;
border-collapse: collapse;
}
.box-table-a th {
font-size: 13px;
font-weight: normal;
padding: 8px;
background: #b9c9fe;
border-top: 4px solid #aabcfe;
border-bottom: 1px solid #fff;
color: #039;
}
.box-table-a td {
padding: 8px;
background: #e8edff;
border-bottom: 1px solid #fff;
color: #669;
border-top: 1px solid transparent;
}
.box-table-a tr:hover td {
background: #d0dafd;
color: #339;
}
view raw styles.css hosted with ❤ by GitHub


Then, open a browser and test the HTML mockup. You should see something similar to the following image:



Thymeleaf Integration


It's time to integrate Thymeleaf with our HTML mockup template. To integrate Thymeleaf we'll use its attribute-based template engine. Browsers will normally ignore unknown HTML attributes, so it won't affect our mockups.

Before we proceed, let me provide you a short description of the specific Thymeleaf attributes we'll be using:

The important attributes
  • The # means to resolve the attribute from the messages bundle
  • The $ means to resolve the attribute from the model
  • The # and $ can be combined together so that messages can be dynamically generated from the model and internationalized from the messages bundle
  • th:fragment="header"

    This allows us to include template fragments from other templates. For example, we can reuse them in footers, headers, and menus. For this tutorial, we won't be reusing the header, but I've added it anyway for future tutorials.
  • th:each="u : ${users}

    This allows us to loop a list of records. This is equivalent to Java's for-loop construct.
  • th:text="${u.id}"

    This allows to dynamically set the label of an element.
  • th:href="@{/users/delete(id=${u.id})}">

    This allows us to define a dynamic URL.
  • th:field="*{id}"

    This allows us to define the field where an input's field will be attached to.
  • th:remove="all"

    This allows us to setup mockup data. Thymeleaf will automatically remove any element contained within this attribute.

Let's now apply these attributes. Here's our updated HTML mockup template:

users.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header">
<meta charset="utf-8" />
<link rel="stylesheet" href="../../resources/css/style.css" th:href="@{/resources/css/style.css}" />
<title th:text="#{user.page.title}">Title</title>
</head>
<body>
<div>
<table class="box-table-a">
<caption th:text="#{user.table.caption}">Site Users</caption>
<thead>
<tr>
<th scope="col" th:text="#{user.id.label}">Id</th>
<th scope="col" th:text="#{user.firstname.label}">First Name</th>
<th scope="col" th:text="#{user.lastname.label}">Last Name</th>
<th scope="col" th:text="#{user.username.label}">Username</th>
<th scope="col" th:text="#{user.role.label}">Role</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="u : ${users}">
<td th:text="${u.id}">1</td>
<td th:text="${u.firstName}">George</td>
<td th:text="${u.lastName}">Washington</td>
<td th:text="${u.username}">gwash</td>
<td th:text="#{${'user.role.' + u.role}}">admin</td>
<td><a href="#" th:href="@{/users/delete(id=${u.id})}">del</a> |
<a href="#" th:href="@{/users/edit(id=${u.id})}">edit</a>
</td>
</tr>
<tr th:remove="all">
<td>2</td>
<td>John</td>
<td>Adams</td>
<td>jadam</td>
<td>user</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr th:remove="all">
<td>3</td>
<td>Thomas</td>
<td>Jefferson</td>
<td>tjeff</td>
<td>user</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr th:remove="all">
<td>4</td>
<td>James</td>
<td>Madison</td>
<td>jmadi</td>
<td>admin</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
<tr th:remove="all">
<td>5</td>
<td>James</td>
<td>Monroe</td>
<td>jmonr</td>
<td>admin</td>
<td><a href="#">del</a> | <a href="#">edit</a></td>
</tr>
</tbody>
</table>
<form action="#" th:action="@{/users/create}" th:object="${commanduser}" method="post">
<table class="box-table-a">
<caption th:text="#{${usertype + '.user.table.caption'}}">New User</caption>
<thead>
<tr>
<th th:text="#{user.firstname.label}">First Name</th>
<th th:text="#{user.lastname.label}">Last Name</th>
<th th:text="#{user.username.label}">Username</th>
<th th:text="#{user.role.label}">Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" hidden="hidden" th:field="*{id}"/>
<input type="text" th:field="*{firstName}"/></td>
<td><input type="text" th:field="*{lastName}"/></td>
<td><input type="text" th:field="*{username}"/></td>
<td><select th:field="*{role}">
<option th:each="role : ${allRoles}" th:value="${role}"
th:text="#{${'user.role.' + role}}">Access Type 1</option>
<option th:remove="all" value="2">Access Type 2</option>
</select></td>
</tr>
<tr>
<td>
<button type="submit" th:text="#{${usertype + '.user.button.label'}}">Action</button>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</form>
</div>
</body>
</html>
view raw users.html hosted with ❤ by GitHub

Internationalization


The th:text attribute allows us to externalize text and with the support of Spring's MessageSource, we are able to parameterize and provide internationalization support.

What is MessageSource?

Strategy interface for resolving messages, with support for the parameterization and internationalization of such messages.

Spring provides two out-of-the-box implementations for production:
  • ResourceBundleMessageSource, built on top of the standard ResourceBundle
  • ReloadableResourceBundleMessageSource, being able to reload message definitions without restarting the VM

Source: Spring 3 Docs: MessageSource

Notice the th:text attributes. Some of them refer to a dot notation object. Where does Thymeleaf retrieve this information?

The information is retrieved from the messages_en.properties resource bundle:

user.page.title=User Management
user.table.caption=Site Users
user.id.label=Id
user.firstname.label=First Name
user.lastname.label=Last Name
user.username.label=Username
user.role.label=Role
user.role.1=admin
user.role.2=regular
new.user.table.caption=New User
new.user.button.label=Add User
update.user.table.caption=Existing User
update.user.button.label=Update User

We've declared that in the ApplicationContext.java configuration (see next section):

// Provides internationalization of messages
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("messages");
return source;
}


The Data transfer object (DTO)


In order for our html page to display data from the Controller, we need to pass a Model attribute. The model attribute is represented by the UserDto.

package org.krams.response;
import java.io.Serializable;
public class UserDto implements Serializable {
private Long id;
private String firstName;
private String lastName;
private String username;
private Integer role;
...getters/setters...
}
view raw UserDto.java hosted with ❤ by GitHub


The fields we declared on the users.html form is based from the fields of the UserDto:

<form action="#" th:action="@{/users/create}" th:object="${commanduser}" method="post">
....
<tr>
<td><input type="text" hidden="hidden" th:field="*{id}"/>
<input type="text" th:field="*{firstName}"/></td>
<td><input type="text" th:field="*{lastName}"/></td>
<td><input type="text" th:field="*{username}"/></td>
<td><select th:field="*{role}">
<option th:each="role : ${allRoles}" th:value="${role}"
th:text="#{${'user.role.' + role}}">Access Type 1</option>
<option th:remove="all" value="2">Access Type 2</option>
</select></td>
</tr>
....
</form>
view raw users.html hosted with ❤ by GitHub


Notice the form has a form-backing object declared named commanduser. Using Thymeleaf's attribute th:object, we're able to declare this form-backing object.

This object passed from the UserController.

@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping
public String getUsersPage(ModelMap model) {
Pageable pageRequest = new PageRequest(0, 10);
Page<User> users = repository.findAll(pageRequest);
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", new UserDto());
model.addAttribute("usertype", "new");
return "users";
}
}


Next

In the next section, we will focus on the configuration layer. We'll study how to declare a JavaConfig-based configuration. We'll also provide an XML-based configuration for comparison purposes. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring and Thymeleaf with JavaConfig (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring and Thymeleaf with JavaConfig (Part 3)

Review


In the previous section, we focused on the view layer and created an HTML mockup template. In this section, we will focus on configuration and declare them using JavaConfig. We will also provide an XML-based config.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Creating the View
    • HTML Mockup
    • Thymeleaf Integration
  3. JavaConfig
    • ApplicationContext.java
    • SpringDataConfig.java
    • ThymeleafConfig.java
    • ApplicationInitializer.java
  4. Layers
    • Domain
    • Service
    • Controller
  5. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

JavaConfig


As stated in the introduction, we will be using JavaConfig-based configuration instead of the usual XML config. However, I don't want to alienate our readers who are used to XML. As a result, I've decided to provide both implementations. However, our focus here is still on JavaConfig.

Note: With JavaConfig, we can now omit the ubiquitous web.xml. But in order to that, we need to run a Servlet 3.0 web container. For this tutorial, I have tested the application with Tomcat 7.0.30 (Maven plugin), 7.0.33 (standalone Tomcat), and Jetty 8.1.5.v20120716 (Maven plugin).

ApplicationContext.java


The ApplicationContext.java contains our main configuration. It's responsible for loading other configurations, either as JavaConfig or XML config.

package org.krams.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@ComponentScan(basePackages = {"org.krams"})
@EnableWebMvc
@Import({SpringDataConfig.class, ThymeleafConfig.class})
@ImportResource("classpath:trace-context.xml")
@PropertySource("classpath:spring.properties")
public class ApplicationContext extends WebMvcConfigurerAdapter {
// Maps resources path to webapp/resources
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
// Only needed if we are using @Value and ${...} when referencing properties
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySources = new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[] {
new ClassPathResource("spring.properties") };
propertySources.setLocations(resources);
propertySources.setIgnoreUnresolvablePlaceholders(true);
return propertySources;
}
// Provides internationalization of messages
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("messages");
return source;
}
}


Let's describe each annotation:
  • @Configuration
    - Marks a class as a JavaConfig
  • @ComponentScan(basePackages = {"org.krams"})
    - Configures scanning of Spring components

    This is equivalent in XML as:
    <context:annotation-config />
    <context:component-scan base-package="org.krams" />

  • @EnableWebMvc

    - Activates Spring's MVC support

    This is equivalent in XML as:
    <mvc:annotation-driven />

  • @Import({SpringDataConfig.class, ThymeleafConfig.class})
    - This allows us to import JavaConfig-based config. Notice we are importing two external configuration classes: SpringDataConfig and ThymeleafConfig
  • @ImportResource("classpath:trace-context.xml")
    - This allows us to import XML-based config files. (As a side note why can't we just declare this as a JavaConfig? It turns out there's no direct translation for the trace-context.xml, so we'll have to import it as XML).

    This is equivalent in XML as:
    <import resource="trace-context.xml"/>

  • @PropertySource("classpath:spring.properties")
    - This allows us to import property files
  • @Bean
    - Declares a Spring bean

Here's the equivalent XML configuration:

<?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:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<context:property-placeholder properties-ref="deployProperties" />
<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />
<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
For example @Controller and @Service. Make sure to set the correct base-package -->
<context:component-scan base-package="org.krams" />
<!-- Configures the annotation-driven Spring MVC Controller programming model.
Note that, with Spring 3.0, this tag works in Servlet MVC only! -->
<mvc:annotation-driven />
<!-- Maps resources path to webapp/resources -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="messages" />
<!-- Imports logging configuration -->
<import resource="trace-context.xml"/>
<!-- Imports datasource configuration -->
<import resource="spring-data.xml"/>
<bean id="deployProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"
p:location="/WEB-INF/spring.properties" />
</beans>


SpringDataConfig.java


The SpringDataConfig.java contains our Spring Data configuration. This is where we declare our data source, transaction manager, and JPA entity manager.

package org.krams.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.mchange.v2.c3p0.ComboPooledDataSource;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("org.krams.repository")
public class SpringDataConfig extends WebMvcConfigurerAdapter {
@Autowired
private Environment env;
// Declare a datasource that has pooling capabilities
@Bean
public DataSource dataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(env.getRequiredProperty("app.jdbc.driverClassName"));
ds.setJdbcUrl(env.getRequiredProperty("app.jdbc.url"));
ds.setUser(env.getRequiredProperty("app.jdbc.username"));
ds.setPassword(env.getRequiredProperty("app.jdbc.password"));
ds.setAcquireIncrement(5);
ds.setIdleConnectionTestPeriod(60);
ds.setMaxPoolSize(100);
ds.setMaxStatements(50);
ds.setMinPoolSize(10);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Declare a JPA entityManagerFactory
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceXmlLocation("classpath*:META-INF/persistence.xml");
em.setPersistenceUnitName("hibernatePersistenceUnit");
em.setDataSource(dataSource());
HibernateJpaVendorAdapter vendor = new HibernateJpaVendorAdapter();
vendor.setShowSql(false);
em.setJpaVendorAdapter(vendor);
return em;
}
// Declare a transaction manager
@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}


Here's the equivalent XML configuration:

<?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:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<context:property-placeholder properties-ref="deployProperties" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Activate Spring Data JPA repository support -->
<jpa:repositories base-package="org.krams.repository" />
<!-- Declare a datasource that has pooling capabilities-->
<bean id="jpaDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="${app.jdbc.driverClassName}"
p:jdbcUrl="${app.jdbc.url}"
p:user="${app.jdbc.username}"
p:password="${app.jdbc.password}"
p:acquireIncrement="5"
p:idleConnectionTestPeriod="60"
p:maxPoolSize="100"
p:maxStatements="50"
p:minPoolSize="10" />
<!-- Declare a JPA entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceXmlLocation="classpath*:META-INF/persistence.xml"
p:persistenceUnitName="hibernatePersistenceUnit"
p:dataSource-ref="jpaDataSource"
p:jpaVendorAdapter-ref="hibernateVendor"/>
<!-- Specify our ORM vendor -->
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:showSql="false"/>
<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
</beans>
view raw spring-data.xml hosted with ❤ by GitHub


ThymeleafConfig.java


The ThymeleafConfig.java contains our Thymeleaf configuration. This is where we declare our Thymeleaf view resolver.

package org.krams.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring3.SpringTemplateEngine;
import org.thymeleaf.spring3.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
@Configuration
public class ThymeleafConfig {
@Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setOrder(1);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
return resolver;
}
}


Here's the equivalent XML configuration:

<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- Declare a Thymeleaf resolver -->
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"
p:prefix="/WEB-INF/templates/"
p:suffix=".html"
p:templateMode="HTML5"
p:order="1"/>
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"
p:templateResolver-ref="templateResolver" />
<bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"
p:templateEngine-ref="templateEngine"/>
</beans>


ApplicationInitializer.java


The ApplicationInitializer.java is the equivalent of web.xml. Here's where we declare the DispatcherServlet.

package org.krams.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class ApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//Load application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationContext.class);
rootContext.setDisplayName("Spring Thymeleaf Tutorial");
//Context loader listener
servletContext.addListener(new ContextLoaderListener(rootContext));
//Dispatcher servlet
ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}


Here's the equivalent XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
version="2.5">
<display-name>Spring Thymeleaf Tutorial</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
view raw web.xml hosted with ❤ by GitHub


Next

In the next section, we will focus on the remaining layers of our Java application. We'll study the Domain, Repository, Service, and Controller layers. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring and Thymeleaf with JavaConfig (Part 3) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring and Thymeleaf with JavaConfig (Part 4)

Review


In the previous section, we declared our configuration using JavaConfig and compared it side-by-side with an XML-based configuration. In this section, we will discuss the remaining layers of our application.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Creating the View
    • HTML Mockup
    • Thymeleaf Integration
  3. JavaConfig
    • ApplicationContext.java
    • SpringDataConfig.java
    • ThymeleafConfig.java
    • ApplicationInitializer.java
  4. Layers
    • Domain
    • Service
    • Controller
  5. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Layers


Here we'll discuss the Domain, Repository, Service and Controller layers.

Domain

Our domain layer consists of two simple classes: User.java and Role.java. If you'd been following my previous tutorials, you will notice that these are the same domain classes we'd been using before. Both classes had been annotated with @Entity which means these are JPA entities and will be persisted to a database.

These classes represent a user with the following properties: first name, last name, username, role, and password (we're not actively using the password field).

For role, we only have two values: an admin or a regular user.

User.java
package org.krams.domain;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import org.codehaus.jackson.annotate.JsonManagedReference;
@Entity(name="user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
@Column(unique=true)
private String username;
private String password;
@JsonManagedReference
@OneToOne(mappedBy="user", cascade={CascadeType.ALL})
private Role role;
public User() {}
...getters/setters...
}
view raw User.java hosted with ❤ by GitHub


Role.java
package org.krams.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import org.codehaus.jackson.annotate.JsonBackReference;
@Entity(name="role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@JsonBackReference
@OneToOne
private User user;
private Integer role;
public Role() {}
...getters/setters...
}
view raw Role.java hosted with ❤ by GitHub


Controller

Our controller is a standard controller providing CRUD requests. The most important lines here are the following:

// Create
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", new UserDto());
model.addAttribute("usertype", "new");

// Update
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", UserMapper.map(repository.findOne(id)));
model.addAttribute("usertype", "update");

These lines adds three attributes to the model:
  • users - contains all users
  • commanduser - the form backing object or the command object of the form
  • usertype - an attribute to determine if the request is a new user or existing user
UserController.java
package org.krams.controller;
import java.util.ArrayList;
import java.util.List;
import org.krams.domain.Role;
import org.krams.domain.User;
import org.krams.repository.UserRepository;
import org.krams.response.UserDto;
import org.krams.service.UserService;
import org.krams.util.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.ui.ModelMap;
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserRepository repository;
@Autowired
private UserService service;
@ModelAttribute("allRoles")
public List<Integer> getAllRoles() {
List<Integer> roles = new ArrayList<Integer>();
roles.add(1);
roles.add(2);
return roles;
}
@RequestMapping
public String getUsersPage(ModelMap model) {
Pageable pageRequest = new PageRequest(0, 10);
Page<User> users = repository.findAll(pageRequest);
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", new UserDto());
model.addAttribute("usertype", "new");
return "users";
}
@RequestMapping(value="/get", produces="application/json")
public @ResponseBody UserDto get(@RequestBody UserDto user) {
return UserMapper.map(repository.findByUsername(user.getUsername()));
}
@RequestMapping(value="/create", produces="application/json", method=RequestMethod.POST)
public String create(UserDto dto) {
if (dto.getId() != null) {
User existingUser = new User(dto.getUsername(),
dto.getFirstName(),
dto.getLastName(),
new Role(dto.getRole()));
service.update(existingUser);
} else {
User newUser = new User(dto.getUsername(),
null,
dto.getFirstName(),
dto.getLastName(),
new Role(dto.getRole()));
service.create(newUser);
}
return "redirect:/users";
}
@RequestMapping(value="/edit")
public String edit(Long id, ModelMap model) {
Pageable pageRequest = new PageRequest(0, 10);
Page<User> users = repository.findAll(pageRequest);
model.addAttribute("users", UserMapper.map(users));
model.addAttribute("commanduser", UserMapper.map(repository.findOne(id)));
model.addAttribute("usertype", "update");
return "users";
}
@RequestMapping(value="/delete")
public String delete(Long id) {
User existingUser = new User();
existingUser.setId(id);
service.delete(existingUser);
return "redirect:/users";
}
}

Repository

We have created two repositories: UserRepository and RoleRepository(not shown). We will be using UserRepository as our primary data access object. Notice we have declared a custom method findByUsername but beyond that, this repository is pretty much standard. UserRepository.java
package org.krams.repository;
import org.krams.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}

Service

Our service layer basically delegates to the repository. It provides an extra logic to filter out duplicate usernames. UserService.java
package org.krams.service;
import org.krams.domain.User;
import org.krams.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository repository;
public Boolean create(User user) {
User existingUser = repository.findByUsername(user.getUsername());
if (existingUser != null)
return false;
user.getRole().setUser(user);
User saved = repository.save(user);
if (saved == null)
return false;
return true;
}
public Boolean update(User user) {
User existingUser = repository.findByUsername(user.getUsername());
if (existingUser == null)
return false;
// Only firstName, lastName, and role fields are updatable
existingUser.setFirstName(user.getFirstName());
existingUser.setLastName(user.getLastName());
existingUser.getRole().setRole(user.getRole().getRole());
User saved = repository.save(existingUser);
if (saved == null)
return false;
return true;
}
public Boolean delete(User user) {
User existingUser = repository.findOne(user.getId());
if (existingUser == null)
return false;
repository.delete(existingUser);
User deletedUser = repository.findOne(user.getId());
if (deletedUser != null)
return false;
return true;
}
}

Next

In the next section, we will study how to build and run our application. We will be using Maven, Tomcat, and Jetty to run the app. We'll also study how to import the project in Eclipse. Click here to proceed.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring and Thymeleaf with JavaConfig (Part 4) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Spring and Thymeleaf with JavaConfig (Part 5)

Review


In the previous section, we have discussed the Domain, Repository, Service, and Controller classes. In this section, we will build and run our app. We will also study how to import the project in Eclipse.

Table of Contents

Click on a link to jump to that section:
  1. Functional Specs
  2. Creating the View
    • HTML Mockup
    • Thymeleaf Integration
  3. JavaConfig
    • ApplicationContext.java
    • SpringDataConfig.java
    • ThymeleafConfig.java
    • ApplicationInitializer.java
  4. Layers
    • Domain
    • Service
    • Controller
  5. Running the application
    • Clone from GitHub
    • Create the Database
    • Run with Maven and Tomcat 7
    • Run with Maven and Jetty 8
    • Import to Eclipse
    • Validate with W3C

Running the Application


Clone from GitHub

To clone from GitHub, follow these instructions:
  1. Open a Git terminal
  2. Enter the following command:
    git clone https://github.com/krams915/spring-thymeleaf-javaconfig.git
    This will clone the JavaConfig-based application.

    If you prefer the XML-based version,
    git clone https://github.com/krams915/spring-thymeleaf-xmlconfig.git

    Remember:

    There are two versions of the application: a JavaConfig-based and an XML config-based app. Both versions are identical in their feature set.

Create the Database

  1. Run MySQL
  2. Create a new database:
    spring_thymeleaf_tutorial
  3. Import the spring_thymeleaf_tutorial.sql from the src/main/resources path

Run with Maven and Tomcat 7

Ensure Maven is installed first, and you have created the MySQL database
  1. Open a terminal
  2. Browse to the directory where you've cloned the project
  3. Enter the following command:
    mvn tomcat7:run
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] -------------------------------------------------------------
    [INFO] Building spring-thymeleaf-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] -------------------------------------------------------------
    [INFO] 
    [INFO] >>> tomcat7-maven-plugin:2.0:run (default-cli) @ spring-thymeleaf-tutorial >>>
    ...
    ...
    ...
    Dec 2, 2012 5:38:31 PM org.apache.coyote.AbstractProtocol init
    INFO: Initializing ProtocolHandler ["http-bio-8080"]
    Dec 2, 2012 5:38:31 PM org.apache.catalina.core.StandardService startInternal
    INFO: Starting service Tomcat
    Dec 2, 2012 5:38:31 PM org.apache.catalina.core.StandardEngine startInternal
    INFO: Starting Servlet Engine: Apache Tomcat/7.0.30
    Dec 2, 2012 5:38:42 PM org.apache.catalina.core.ApplicationContext log
    INFO: Spring WebApplicationInitializers detected on classpath: [org.krams.config.ApplicationInitializer@1cc33893]
    Dec 2, 2012 5:38:43 PM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring root WebApplicationContext
    Dec 2, 2012 5:38:52 PM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring FrameworkServlet 'dispatcher'
    Dec 2, 2012 5:38:52 PM org.apache.coyote.AbstractProtocol start
    INFO: Starting ProtocolHandler ["http-bio-8080"]
    
  5. Open a browser
  6. Visit the following URL:
    http://localhost:8080/spring-thymeleaf-tutorial/users

Run with Maven and Jetty 8

Ensure Maven is installed first, and you have created the MySQL database.
  1. Open a terminal
  2. Browse to the directory where you've cloned the project
  3. Enter the following command:
    mvn jetty:run
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] -------------------------------------------------------------
    [INFO] Building spring-thymeleaf-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] -------------------------------------------------------------
    [INFO] 
    [INFO] >>> jetty-maven-plugin:8.1.5.v20120716:run (default-cli) @ spring-thymeleaf-tutorial >>>
    ...
    ...
    ...
    2012-12-02 17:42:56.556:INFO:/spring-thymeleaf-tutorial:Initializing Spring FrameworkServlet 'dispatcher'
    2012-12-02 17:42:56.760:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080
    [INFO] Started Jetty Server
    
  5. Open a browser
  6. Visit the following URL:
    http://localhost:8080/spring-thymeleaf-tutorial/users

Import to Eclipse

Ensure Maven is installed first
  1. Open a terminal
  2. Enter the following command:
    mvn eclipse:eclipse -Dwtpversion=2.0
  3. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] -------------------------------------------------------------
    [INFO] Building spring-thymeleaf-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] -------------------------------------------------------------
    [INFO] 
    [INFO] >>> maven-eclipse-plugin:2.9:eclipse (default-cli) @ spring-thymeleaf-tutorial >>>
    ...
    ...
    ... 
    [INFO] -------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] -------------------------------------------------------------
    [INFO] Total time: 9.356s
    [INFO] Finished at: Sun Dec 02 17:46:43 PHT 2012
    [INFO] Final Memory: 15M/81M
    [INFO] -------------------------------------------------------------
    

    This command will add the following files to your project:
    .classpath
    .project
    .settings
    target
    You may have to enable "show hidden files" in your file explorer to view them.
  4. Run Eclipse and import the application as Maven project

Validate with W3C Markup Validation Service

One of the promises of Thymeleaf is it produces valid HTML pages. Let's test that out using W3C validation service.
  1. Run the application
  2. Open a browser
  3. Visit the following URL:
    http://localhost:8080/spring-thymeleaf-tutorial/users
  4. View the HTML source (right-click or go to menu)
  5. Copy the HTML source
  6. Open W3C Markup Validation Service at http://validator.w3.org/#validate_by_input
  7. Paste the HTML source and wait for the validation result

    You should see a similar output:

Conclusion


We've have completed our Spring MVC application with Thymeleaf as our template engine . We've studied how to convert our HTML mockup into a Thymeleaf template that validates with W3C validator service. We've also discussed how to create a JavaConfig-based application. As a bonus, we've also provided an XML-based application.

I hope you've enjoyed this tutorial. Don't forget to check my other tutorials at the Tutorials section.

Revision History

Revision Date Description
1 Dec 2 2012 Uploaded tutorial and GitHub repositories
2 Dec 3 2012 Updated table of contents
3 Dec 12 2012 Updated Part 2

StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring and Thymeleaf with JavaConfig (Part 5) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share