Spring MVC Tutorial – Using Tiles 2
This post is also based on my previous work:
- Spring MVC Tutorial – Hibernate Integration – build a Spring MVC app using Hibernate from the ground up.
- Spring MVC Tutorial – Paging through Hibernate and Selection Handling – add table/list browsing support utilizing paging in Hibernate, add a page navigation bar, add checkbox handling against table rows.
Here I will not add any new functionality – just move the view technology to Tiles 2 that comes bundled with Spring complete distribution.
The new completed project is available as ibank-v3.zip.
Till now, I have repeated header/footer/menu code in all the JSP files. Tiles 2 can be used to remove that repetition. Let’s begin.
Required Libraries
I’m using Spring 2.5.6.SEC01. The full distribution comes with the following Tiles 2 jars under lib/tiles:
- tiles-api-2.0.6.jar
- tiles-core-2.0.6.jar
- tiles-jsp-2.0.6.jar
Some Jakarta Commons libraries are mandatory dependencies for Tiles 2. Include them as well from lib/jakarta-commons:
- commons-beanutils.jar
- commons-digester.jar
- commons-logging.jar (already included as a core Spring dependency, so no need to include again)
Caution: Spring 2.5.6 does not work with Tiles 2 2.1.1 onwards. So, we use the old 2.0.6 version supplied with Spring. Spring 3.0 solves this issue.
Typical Tiles 2 without Spring
If I use Tiles 2 without Spring MVC integration then I have two ways to include it in web.xml:
Option 1 – Using a Servlet Definition
<servlet>
<servlet-name>tiles2</servlet-name>
<servlet-class>org.apache.tiles.web.startup.TilesServlet</servlet-class>
<init-param>
<param-name>org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG</param-name>
<param-value>/WEB-INF/tiles-defs.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
Note that a servlet mapping is not required as tiles doesn’t serve any requests directly.
Option 2 – Using a Listener
<listener>
<listener-class>org.apache.tiles.web.startup.TilesListener</listener-class>
</listener><context-param>
<param-name>org.apache.tiles.impl.BasicTilesContainer.DEFINITIONS_CONFIG</param-name>
<param-value>/WEB-INF/tiles/defs/tiles-defs.xml</param-value>
</context-param>
Multiple listener and context-param definitions can co-exist in a single web.xml.
In our case, none of the above is required as Spring takes care of setting up Tiles 2.
Defining the Layout and Common Parts
First, create the folder WEB-INF\tiles\layout and create a common layout template (classic.jsp) for the site:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><tiles:getAsString name="title"/></title>
<link rel="stylesheet" href="css/master.css" type="text/css" />
</head>
<body>
<div id="mask">
<div id="header">
<tiles:insertAttribute name="header"/>
</div>
<div id="content-wrapper">
<div id="leftcol-wrapper">
<div id="leftcol"><tiles:insertAttribute name="menu"/></div>
<div id="content">
<tiles:insertAttribute name="body"/>
</div>
</div>
</div>
<div id=clearfooter"></div>
</div>
<div id="footer" align="center">
<tiles:insertAttribute name="footer"/>
</div>
</body>
</html>
It’s based on the admin.jsp that I used earlier as a starting point for new JSPs to maintain the layout consistency. Note the tiles taglib at the beginning. The <tiles:…> elements are placeholders that will be filled up by view definitions later on.
The header.jsp and footer.jsp created under tiles folder are very simple serving my purpose.
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<table border="0" cellspacing="0" width="100%">
<tr>
<td align="center"><h2>iBank – Web Banking Portal</h2></td>
<td width="50px" align="center" valign="middle">
<b><font style="size: 1.5em; color: yellow">iBank</font></b>
</td>
</tr>
</table>
footer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
© 2009 Himu
Now, create the tiles/admin folder put menu.jsp in it. This file holds the administration menu.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<ul>
<li><a href="<%=request.getContextPath()%>/adminhome.htm">Admin Home</a></li>
<li><a href="<%=request.getContextPath()%>/createcust.htm">Create New Customer</a></li>
<li><a href="<%=request.getContextPath()%>/newcustlist.htm">Customer List</a></li>
</ul>
Cleaning the Old JSPs
Now that I’m using Tiles 2, I have to remove the layout code put in each JSP earlier. I’m not going to show each modified JSP. Please look at the attached project. But for a taste of things, here is the new admin.jsp, stripped of all layouts, CSS, and HTML extras:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<center>
<h1>iBank Administration</h1>
<p>Use the menu for navigation.</p>
</center>
I’m also bringing the home page within the template:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<h1 align="center">Welcome to iBank</h1>
<h2 align="center">Your Online Bank Portal</h2>
<p align="center">
Today is ${today}.<br/>
<a href="adminhome.htm">Administration Site</a>
</p>
The rest follows a similar pattern.
Writing the View Definitions
Just for demonstration, I’m splitting the definitions in two files – one for normal views, the other for admin site views. The definitions are created in the tiles/defs folder. Here is tiles-defs.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd" >
<tiles-definitions><definition name="site.common" template="/WEB-INF/tiles/layout/classic.jsp">
<put-attribute name="header" value="/WEB-INF/tiles/header.jsp"/>
<!–<put-attribute name="title" value=""/>–>
<!–<put-attribute name="menu" value=""/>–>
<!–<put-attribute name="body" value=""/>–>
<put-attribute name="footer" value="/WEB-INF/tiles/footer.jsp"/>
</definition><definition name="site.home" extends="site.common">
<put-attribute name="title" value="iBank – Home"/>
<put-attribute name="menu" value=""/>
<put-attribute name="body" value="/WEB-INF/jsp/home.jsp"/>
</definition>
</tiles-definitions>
site.common is the root view definition. It is an abstract definition as title, menu, and body required for classic.jsp are not specified. site.home, for the home page, extends this definition and provides the missing parts.
admin-defs.xml contains the view definitions for the the admin part of the site:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.0//EN" "http://tiles.apache.org/dtds/tiles-config_2_0.dtd" >
<tiles-definitions><definition name="admin.common" extends="site.common">
<!–<put-attribute name="header" value="/WEB-INF/tiles/header.jsp"/>–>
<!–<put-attribute name="title" value=""/>–>
<put-attribute name="menu" value="/WEB-INF/tiles/admin/menu.jsp"/>
<!–<put-attribute name="body" value=""/>–>
<!–<put-attribute name="footer" value="/WEB-INF/tiles/footer.jsp"/>–>
</definition><definition name="admin.home" extends="admin.common">
<put-attribute name="title" value="Administration – Home"/>
<put-attribute name="body" value="/WEB-INF/jsp/admin/admin.jsp"/>
</definition>
<definition name="admin.createcust" extends="admin.common">
<put-attribute name="title" value="Register New Customer"/>
<put-attribute name="body" value="/WEB-INF/jsp/admin/newcustomer.jsp"/>
</definition><definition name="admin.createcust-success" extends="admin.common">
<put-attribute name="title" value="Customer Registration Successful"/>
<put-attribute name="body" value="/WEB-INF/jsp/admin/newcustomer-success.jsp"/>
</definition><definition name="admin.newcustlist" extends="admin.common">
<put-attribute name="title" value="New/Unauthorized Customers"/>
<put-attribute name="body" value="/WEB-INF/jsp/admin/newcustlist.jsp"/>
</definition><definition name="admin.createcust" extends="admin.common">
<put-attribute name="title" value="Register New Customer"/>
<put-attribute name="body" value="/WEB-INF/jsp/admin/newcustomer.jsp"/>
</definition></tiles-definitions>
admin.common builds on site.common and specifies the default for menu. It is still abstract (as title and body are not defined) and used as the base for the other admin views.
Modifying the Controllers
The controllers that explicitly mention view names need to be modified as well – just change the old admin/xyz names to admin.xyz as I’ve kept the patterns consistent.
Integrating Tiles 2
I’m using Tiles 2 with Spring MVC. So, no need to modify web.xml. Instead, I have to add a TilesConfigurer in ibank-servlet.xml – the Spring bean definition file for the WebAppliationContext. The steps are as follows:
- Tell Spring MVC to configure Tiles 2.
- Define a new view resolver for resolving Tiles 2 view names.
- Change all necessary view names in the file.
The modified file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"><!– Tiles 2 –>
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles/defs/tiles-defs.xml</value>
<value>/WEB-INF/tiles/defs/admin-defs.xml</value>
</list>
</property>
</bean>
<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/home.htm">homePageController</prop>
<prop key="/adminhome.htm">adminHomePageController</prop>
<prop key="/createcust.htm">newCustomerController</prop>
<prop key="/newcustlist.htm">newCustomerListController</prop>
<prop key="/authorizecust.htm">authorizeCustController</prop>
</props>
</property>
</bean><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
<property name="order" value="2"/>
</bean>
<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
<property name="order" value="1"/>
</bean><bean id="homePageController" class="org.himu.ibank.controller.HomePageController"/>
<bean id="adminHomePageController" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
<property name="viewName" value="admin.home"/>
</bean><bean id="newCustomerController" class="org.himu.ibank.controller.NewCustomerController">
<property name="custRegService" ref="customerRegService"/>
<property name="formView" value="admin.newcustomer"/>
<property name="successView" value="admin.newcustomer-success"/>
<property name="commandName" value="customer"/>
<property name="commandClass" value="org.himu.ibank.domain.Customer"/>
</bean><bean id="newCustomerListController" class="org.himu.ibank.controller.NewCustListController">
<property name="custRegService" ref="customerRegService"/>
</bean><bean id="authorizeCustController" class="org.himu.ibank.controller.AuthorizeCustController">
<property name="custRegService" ref="customerRegService"/>
</bean>
</beans>
Step 1 – The tilesConfigurer bean definition tells Spring MVC to configure Tiles 2 and tells it to use tiles-defs.xml and admin-defs.xml for the view definitions.
Step 2 – The view processor for Tiles 2 in Spring MVC is TilesView. It cannot be used with the InternalResourceViewResolver that I have been using so far. So, I create another view resolver and specify the ordering to Spring MVC so that for any view name, the tilesViewResolver is tried first and then fallback to the default view resolver.
Step 3 – The view names are changed as appropriate.
Done
That concludes the changes for integrating Tiles 2 to the ibank application. Compile, deploy and test. The behavior remains exactly the same.