7.6. The Simple Web Application Module
The web application is defined in a simple-webapp
project. This
simple web application project is going to define two Spring MVC
Controllers: WeatherController
and simple-weather
and the
applicationContext-persist.xml
file in simple-persist
. The
component architecture of this simple web application is shown in
Figure 7.3, “Spring MVC Controllers Referencing Components in simple-weather and simple-persist.”.
The POM for simple-webapp
is shown in
POM for simple-webapp.
POM for simple-webapp.
<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>
<parent>
<groupId>org.sonatype.mavenbook.multispring</groupId>
<artifactId>simple-parent</artifactId>
<version>1.0</version>
</parent>
<artifactId>simple-webapp</artifactId>
<packaging>war</packaging>
<name>Simple Web Application</name>
<dependencies>
<dependency> (1)
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.sonatype.mavenbook.multispring</groupId>
<artifactId>simple-weather</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.sonatype.mavenbook.multispring</groupId>
<artifactId>simple-persist</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
<build>
<finalName>simple-webapp</finalName>
<plugins>
<plugin> (2)
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<dependencies> (3)
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId> (4)
<artifactId>hibernate3-maven-plugin</artifactId>
<version>2.0</version>
<configuration>
<components>
<component>
<name>hbm2ddl</name>
<implementation>annotationconfiguration</implementation> (5)
</component>
</components>
</configuration>
<dependencies>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
As this book progresses and the examples become more and more
substantial, you’ll notice that the pom.xml
begins to take on some
weight. In this POM, we’re configuring four dependencies and two
plugins. Let’s go through this POM in detail and dwell on some of the
important configuration points:
(1)
|
This simple-webapp project defines four dependencies: the
Servlet 2.4 specification, the simple-weather service library, the
simple-persist persistence library, and the entire Spring Framework
2.0.7.
|
(2)
|
The Maven Jetty plugin couldn’t be easier to add to this project;
we simply add a plugin element that references the appropriate
groupId and artifactId . The fact that this plugin is so trivial to
configure means that the plugin developers did a good job of providing
adequate defaults that don’t need to be overridden in most cases. If
you did need to override the configuration of the Jetty plugin, you
would do so by providing a configuration element.
|
(3)
|
In our build configuration, we’re going to be configuring the
Maven Hibernate3 Plugin to hit an embedded HSQLDB instance. For the
Maven Hibernate 3 plugin to successfully connect to this database
using JDBC, the plugin will need to reference the HSQLDB JDBC driver on
the classpath. To make a dependency available for a plugin, we add a
dependency declaration right inside the plugin declaration. In this case,
we’re referencing hsqldb:hsqldb:1.8.0.7. The Hibernate plugin also
needs the JDBC driver to create the database, so we have also added
this dependency to its configuration.
|
(4)
|
The Maven Hibernate plugin is when this POM starts to get
interesting. In the next section, we’re going to run the hbm2ddl
goal to generate a HSQLDB database. In this pom.xml , we’re including
a reference to version 2.0 of the hibernate3-maven-plugin hosted by
the Codehaus Mojo plugin.
|
(5)
|
The Maven Hibernate3 plugin has different ways to obtain Hibernate
mapping information that are appropriate for different usage scenarios
of the Hibernate3 plugin. If you were using Hibernate Mapping XML
(.hbm.xml ) files, and you wanted to generate model classes using the
hbm2java goal, you would set your implementation to
configuration . If you were using the Hibernate3 plugin to reverse
engineer a database to produce .hbm.xml files and model classes from
an existing database, you would use an implementation of
jdbcconfiguration . In this case, we’re simply using an existing
annotated object model to generate a database. In other words, we have
our Hibernate mapping, but we don’t yet have a database. In this usage
scenario, the appropriate implementation value is
annotationconfiguration . The Maven Hibernate3 plugin is discussed in
more detail in the later section
Section 7.7, “Running the Web Application”.
|
Next, we turn our attention to the two Spring MVC controllers that
will handle all of the requests. Both of these controllers reference
the beans defined in simple-weather
and simple-persist
.
simple-webapp WeatherController.
package org.sonatype.mavenbook.web;
import org.sonatype.mavenbook.weather.model.Weather;
import org.sonatype.mavenbook.weather.persist.WeatherDAO;
import org.sonatype.mavenbook.weather.WeatherService;
import javax.servlet.http.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class WeatherController implements Controller {
private WeatherService weatherService;
private WeatherDAO weatherDAO;
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response)
throws Exception {
String zip = request.getParameter("zip");
Weather weather = weatherService.retrieveForecast(zip);
weatherDAO.save(weather);
return new ModelAndView("weather", "weather", weather);
}
public WeatherService getWeatherService() {
return weatherService;
}
public void setWeatherService(WeatherService weatherService) {
this.weatherService = weatherService;
}
public WeatherDAO getWeatherDAO() {
return weatherDAO;
}
public void setWeatherDAO(WeatherDAO weatherDAO) {
this.weatherDAO = weatherDAO;
}
}
WeatherController
implements the Spring MVC Controller interface
that mandates the presence of a handleRequest()
method with the
signature shown in the example. If you look at the meat of this
method, you’ll see that it invokes the retrieveForecast()
method on
the weatherService
instance variable. Unlike the previous chapter,
which had a Servlet that instantiated the WeatherService
class, the
WeatherController
is a bean with a weatherService
property. The
Spring IoC container is responsible for wiring the controller to the
weatherService
component. Also notice that we’re not using the
WeatherFormatter
in this Spring controller implementation; instead,
we’re passing the Weather
object returned by retrieveForecast()
to
the constructor of ModelAndView
. This ModelAndView
class is going
to be used to render a Velocity template, and this template will have
references to a ${weather}
variable. The weather.vm
template
is stored in src/main/webapp/WEB-INF/vm
and is shown in
weather.vm Template Rendered by WeatherController.
In the WeatherController
, before we render the output of the
forecast, we pass the Weather
object returned by the
WeatherService
to the save()
method on WeatherDAO
. Here we are
saving this Weather
object—using Hibernate—to an HSQLDB
database. Later, in HistoryController
, we will see how we can
retrieve a history of weather forecasts that were saved by the
WeatherController
.
weather.vm Template Rendered by WeatherController.
<b>Current Weather Conditions for:
${weather.location.city}, ${weather.location.region},
${weather.location.country}</b><br/>
<ul>
<li>Temperature: ${weather.condition.temp}</li>
<li>Condition: ${weather.condition.text}</li>
<li>Humidity: ${weather.atmosphere.humidity}</li>
<li>Wind Chill: ${weather.wind.chill}</li>
<li>Date: ${weather.date}</li>
</ul>
The syntax for this Velocity template is straightforward: variables
are referenced using ${}
notation. The expression between the
curly braces references a property, or a property of a property on the
weather
variable, which was passed to this template by the
WeatherController
.
The HistoryController
is used to retrieve recent forecasts that have
been requested by the WeatherController
. Whenever we retrieve a
forecast from the WeatherController
, that controller saves the
Weather
object to the database via the WeatherDAO
. WeatherDAO
then uses Hibernate to dissect the Weather
object into a series of
rows in a set of related database tables. The HistoryController
is
shown in simple-web HistoryController.
simple-web HistoryController.
package org.sonatype.mavenbook.web;
import java.util.*;
import javax.servlet.http.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.sonatype.mavenbook.weather.model.*;
import org.sonatype.mavenbook.weather.persist.*;
public class HistoryController implements Controller {
private LocationDAO locationDAO;
private WeatherDAO weatherDAO;
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String zip = request.getParameter("zip");
Location location = locationDAO.findByZip(zip);
List<Weather> weathers = weatherDAO.recentForLocation( location );
Map<String,Object> model = new HashMap<String,Object>();
model.put( "location", location );
model.put( "weathers", weathers );
return new ModelAndView("history", model);
}
public WeatherDAO getWeatherDAO() {
return weatherDAO;
}
public void setWeatherDAO(WeatherDAO weatherDAO) {
this.weatherDAO = weatherDAO;
}
public LocationDAO getLocationDAO() {
return locationDAO;
}
public void setLocationDAO(LocationDAO locationDAO) {
this.locationDAO = locationDAO;
}
}
The HistoryController
is wired to two DAO objects defined in
simple-persist
. The DAOs are bean properties of the
HistoryController
: WeatherDAO
and LocationDAO
. The goal of the
HistoryController
is to retrieve a List
of Weather
objects which
correspond to the zip
parameter. When the WeatherDAO
saves the
Weather
object to the database, it doesn’t just store the zip code,
it stores a Location
object which is related to the Weather
object
in the simple-model
. To retrieve a List
of Weather
objects, the
HistoryController
first retrieves the Location
object that
corresponds to the zip
parameter. It does this by invoking the
findByZip()
method on LocationDAO
.
Once the Location
object has been retrieved, the HistoryController
will then attempt to retrieve recent Weather
objects that match the
given Location
. Once the List<Weather>
has been retrieved, a
HashMap
is created to hold two variables for the history.vm
Velocity template shown in history.vm Rendered by the HistoryController.
history.vm Rendered by the HistoryController.
<b>
Weather History for: ${location.city}, ${location.region}, ${location.country}
</b>
<br/>
#foreach( $weather in $weathers )
<ul>
<li>Temperature: $weather.condition.temp</li>
<li>Condition: $weather.condition.text</li>
<li>Humidity: $weather.atmosphere.humidity</li>
<li>Wind Chill: $weather.wind.chill</li>
<li>Date: $weather.date</li>
</ul>
#end
The history.vm
template in src/main/webapp/WEB-INF/vm
references
the location
variable to print out information about the location of
the forecasts retrieved from the WeatherDAO
. This template then uses
a Velocity control structure, #foreach
, to loop through each element
in the weathers
variable. Each element in weathers
is assigned to
a variable named weather
and the template between #foreach
and
#end
is rendered for each observation.
You’ve seen these Controller
implementations, and you’ve seen that
they reference other beans defined in simple-weather
and
simple-persist
, they respond to HTTP requests, and they yield
control to some mysterious templating system that knows how to render
Velocity templates. All of this magic is configured in a Spring
application context in
src/main/webapp/WEB-INF/weather-servlet.xml
. This XML configures the
controllers and references other Spring-managed beans. It is loaded by
a ServletContextListener
which is also configured to load the
applicationContext-weather.xml
and applicationContext-persist.xml
from the classpath. Let’s take a closer look at the
weather-servlet.xml
shown in Spring Controller Configuration weather-servlet.xml.
Spring Controller Configuration weather-servlet.xml.
<beans>
<bean id="weatherController" (1)
class="org.sonatype.mavenbook.web.WeatherController">
<property name="weatherService" ref="weatherService"/>
<property name="weatherDAO" ref="weatherDAO"/>
</bean>
<bean id="historyController"
class="org.sonatype.mavenbook.web.HistoryController">
<property name="weatherDAO" ref="weatherDAO"/>
<property name="locationDAO" ref="locationDAO"/>
</bean>
<!-- you can have more than one handler defined -->
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.
SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/weather.x"> (2)
<ref bean="weatherController" />
</entry>
<entry key="/history.x">
<ref bean="historyController" />
</entry>
</map>
</property>
</bean>
<bean id="velocityConfig" (3)
class="org.springframework.web.servlet.view.velocity.
VelocityConfigurer">
<property name="resourceLoaderPath" value="/WEB-INF/vm/"/>
</bean>
<bean id="viewResolver" (4)
class="org.springframework.web.servlet.view.velocity.
VelocityViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".vm"/>
<property name="exposeSpringMacroHelpers" value="true"/>
</bean>
</beans>
(1)
|
The weather-servlet.xml defines the two controllers as
Spring-managed beans. weatherController has two properties which are
references to weatherService and weatherDAO . historyController
references the beans weatherDAO and locationDAO . When this
ApplicationContext is created, it is created in an environment that
has access to the ApplicationContext s defined in both
simple-persist and simple-weather . In web.xml for simple-webapp you
will see how Spring is configured to merge components from multiple
Spring configuration files.
|
(2)
|
The urlMapping bean defines the URL patterns which invoke the
WeatherController and the HistoryController . In this example, we
are using the SimpleUrlHandlerMapping and mapping /weather.x to
WeatherController and /history.x to HistoryController .
|
(3)
|
Since we are using the Velocity templating engine, we will need to
pass in some configuration options. In the velocityConfig bean, we
are telling Velocity to look for all templates in the /WEB-INF/vm
directory.
|
(4)
|
Last, the viewResolver is configured with the class
VelocityViewResolver . There are a number of ViewResolver
implementations in Spring from a standard ViewResolver to render JSP
or JSTL pages to a resolver which can render Freemarker templates. In
this example, we’re configuring the Velocity templating engine and
setting the default prefix and suffix which will be automatically
appended to the names of the template passed to ModelAndView .
|
Finally, the simple-webapp
project was a web.xml
which provides
the basic configuration for the web application. The web.xml
file is
shown in web.xml for simple-webapp.
web.xml for simple-webapp.
<web-app id="simple-webapp" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>Simple Web Application</display-name>
<context-param> (1)
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-weather.xml
classpath:applicationContext-persist.xml
</param-value>
</context-param>
<context-param> (2)
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener> (3)
<listener-class>
org.springframework.web.util.Log4jConfigListener
</listener-class>
</listener>
<listener>
<listener-class> (4)
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet> (5)
<servlet-name>weather</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping> (6)
<servlet-name>weather</servlet-name>
<url-pattern>*.x</url-pattern>
</servlet-mapping>
</web-app>
(1)
|
Here’s a bit of magic which allows us to reuse the
applicationContext-weather.xml and applicationContext-persist.xml
in this project. The contextConfigLocation is used by the
ContextLoaderListener to create an ApplicationContext . When the
weather servlet is created, the weather-servlet.xml from
Spring Controller Configuration weather-servlet.xml is going to be evaluated with the
ApplicationContext created from this contextConfigLocation . In
this way, you can define a set of beans in another project and you can
reference these beans via the classpath. Since the simple-persist
and simple-weather JARs are going to be in WEB-INF/lib , all we do
is use the classpath: prefix to reference these files. (Another
option would have been to copy these files to /WEB-INF and reference
them with something like /WEB-INF/applicationContext-persist.xml .)
|
(2)
|
The log4jConfigLocation is used to tell the
Log4JConfigListener where to look for Log4J logging
configuration. In this example, we tell Log4J to look in
/WEB-INF/log4j.properties .
|
(3)
|
This makes sure that the Log4J system is configured when the web
application starts. It is important to put this Log4JConfigListener
before the ContextLoaderListener ; otherwise, you may miss important
logging messages which point to a problem preventing application
startup. If you have a particularly large set of beans managed by
Spring, and one of them happens to blow up on application startup,
your application will fail. If you have logging initialized before
Spring starts, you might have a chance to catch a warning or an
error. If you don’t have logging initialized before Spring starts up,
you’ll have no idea why your application refuses to start.
|
(4)
|
The ContextLoaderListener is essentially the Spring
container. When the application starts, this listener will build an
ApplicationContext from the contextConfigLocation parameter.
|
(5)
|
We define a Spring MVC DispatcherServlet with a name of
weather . This will cause Spring to look for a Spring configuration
file in /WEB-INF/weather-servlet.xml . You can have as many
DispatcherServlet s as you need; a DispatcherServlet can contain
one or more Spring MVC Controller implementations.
|
(6)
|
All requests ending in .x will be routed to the weather
servlet. Note that the .x extension has no particular meaning; it is
an arbitrary choice and you can use whatever URL pattern you like.
|