7.6. simple-webapp模块

该web应用中项目simple-webapp中定义。这个简单web应用项目将会定义两个Spring MVC控制器:WeatherControllerHistoryController。两者都会引用simple-weathersimple-persist中定义的组件。Spring容器在应用程序的web.xml中配置,该文件引用了simple-weather中的applicationContext-weather.xml文件和simple-persist中的applicationContext-persist.xml文件。这个简单web应用的组件架构如Figure 7.3, “Spring MVC 控制器引用simple-weather和simple-persist中的组件”显示。

Spring MVC 控制器引用simple-weather和simple-persist中的组件

Figure 7.3. Spring MVC 控制器引用simple-weather和simple-persist中的组件


simple-webapp的POM如Example 7.12, “simple-webapp的POM”显示。

Example 7.12. simple-webapp的POM

<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.ch07</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>

  <artifactId>simple-webapp</artifactId>
  <packaging>war</packaging>
  <name>Simple Web Application</name>
  <dependencies>
    <dependency> 
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-servlet_2.4_spec</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-weather</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</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> 
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <dependencies>
          <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
          </dependency>
        </dependencies>        
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId> 
        <artifactId>hibernate3-maven-plugin</artifactId>
        <version>2.0</version>
        <configuration>
          <components>
            <component>
              <name>hbm2ddl</name>
              <implementation>annotationconfiguration</implementation> 
            </component>
          </components>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
          </dependency>
        </dependencies>        
      </plugin>
    </plugins>
  </build>
</project>

随着书本的推进以及样例变得越来越大,你会注意到pom.xml开始呈现得有一些笨重,这里我们配置了四个依赖和两个插件。让我们详细查看一下这个POM然后详述其中一些重要的配置点:

1

simple-webapp项目定义了四个依赖:来自于Apache Geronimo的Servlet 2.4规格说明实现,simple-weather服务类库,simple-persist持久化类库,以及整个Spring Framework 2.0.7。

2

Maven Jetty插件以最简单的方式加入到该项目,我们只是添加一个引用了对应groupIdartifactIdplugin元素。配置这个插件如此平常意味着这个插件的开发者做了很好的工作提供了足够的默认值,在大部分情况下不需要被重写。如果你需要重写一些Jetty插件的配置,那么就需要提供configuration元素。

3

在我们的build配置中,我们还配置了Maven Hibernate3插件来访问内嵌的HSQLDB实例。要让Maven Hibernate3插件能成功的使用JDBC连接该数据库,该插件需要引用classpath中的HSQLDB JDBC驱动。为了让这个插件能使用该依赖,我们在plugin声明下面添加了一个dependency声明。在该例中,我们引用了hsqldb:hsqldb:1.8.0.7。这个Hibernate插件也需要JDBC驱动来创建数据库,所以我们也在它的配置中添加了这个依赖。

4

这个Maven Hibernate插件正是该POM变得有趣的地方。在下一节,我们将会运行hbm2ddl目标来生成HSQLDB数据库。在这个pom.xml中,我们包含了对hibernate3-maven-plugin版本2.0的引用,该插件由Codehaus Mojo维护。

5

Maven Hibernate3插件有不同的方法获取Hibernate映射信息,这些信息适用于Hibernate3插件的不同用例。如果你正在使用Hibernate映射XML文件(.hbm.xml),你会要使用hbm2java目标生成模型类,你会将implementation设置成configuration。如果你使用Hibernate3插件逆向工程从一个数据库产生.hbm.xml文件和模型类,你会需要一个jdbcconfiguration的implementation。在本例中,我们使用现存的标注对象模型来生成一个数据库。换句话说,我们有我们的Hibernate映射,但我们还没有数据库。在这种用例中,正确的implementation值应该是annotationconfiguration。Maven Hibernate3插件在后面的一节Section 7.7, “运行这个Web应用”中详细讨论。

Note

一个常见的错误是使用extensions配置添加一个插件需要的依赖。这是强烈不推荐的因为extensions会在你的项目中造成classpath污染,以及其它令人讨厌的副作用。此外,extensions行为正在2.1中被重做,最后你都会要改变它。唯一的对extensions的正常使用是定义新的wagon实现。

接下来,我们将我们的注意力转移到两个处理所有请求的Spring MVC控制器。两个控制器都引用了在simple-weathersimple-persist中定义的bean。

Example 7.13. 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实现了MVC Controller接口,该接口强制要求实现如上例中的handleRequest()方法。如果你看一下该方法的主要内容,你会看到它调用了weatherService实例变量的retrieveForecast()方法。不像前面的章节中,有一个Servlet来初始化WeatherService类,WeatherController是一个带有weatherService属性的bean。Spring Ioc容器会负责将weatherService组件注入到控制器。同时也注意我们并没有在这个控制器实现中使用WeatherFormatter;而是将retrieveForecast()返回的Weather对象传递给了ModelAndView的构造函数。ModelAndView类将被用来呈现Velocity模板,这个模板有对${weather}变量的引用。weather.vm模板存储在src/main/webapp/WEB-INF/vm,如???所示。

在这个WeatherController中,在我们呈现天气预报输出之前,我们将WeatherService返回的Weather对象传递给WeatherDAOsave()方法。这里我们使用Hibernate将Weather对象保存到HSQLDB数据库。之后,在HistoryController中,我们将看如何能够获取由WeatherController保存的天气预报历史信息。

Example 7.14. 由 WeatherController 呈现的 weather.vm 模板

<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>

Velocity模板的语法简单易懂,变量通过${}标记引用。大括弧里面的表达式引用weather变量的一个属性,或者该变量属性的属性。weather变量是由WeatherController传递给该模板的。

HistoryController用来获取那些已经由WeatherController请求过的最近历史天气预报信息。任何时候当我们从WeatherController获取预报的时候,该控制器通过WeatherDAOWeather对象保存至数据库。WeatherDAO然后使用Hibernate将Weather对象剖析成一组相关数据库表的记录行。HistoryControllerExample 7.15, “simple-web 的 HistoryController”所示。

Example 7.15. 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;
  }
}

HistoryController被注入了两个定义在simple-persist中的DAO对象。这两个DAOHistoryController的bean属性:WeatherDAOLocationDAOHistoryController的目标是获取一个与zip参数对应的Weather对象列表。当WeatherDAOWeather对象保存至数据库,它不只是保存邮编,它保存了一个与simple-modelWeather对象相关的Location对象。为了获取一个Weather对象的List,首先通过调用LocationDAOfindByZip()方法,获取与zip参数对应的Location对象。

一旦获得了Location对象,HistoryController之后就会尝试获取与给定的Location相匹配的Weather对象。在获取了List<Weather>之后,一个HashMap被创建以存储两个变量,供如???中显示的history.vm Velocity模板使用。

Example 7.16. 由 HistoryController 呈现的 history.vm

<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

src/main/webapp/WEB-INF/vm中的history.vm模板引用了location变量以输出天气预报位置的相关信息。该模板使用了一个Velocity的控制结构,为了循环weathers变量中的每个元素。weathers中的每个元素被赋给了一个名为weather的变量,#foreach#end中间的模板用来呈现每个预报输出。

你已经看到了这些Controller实现,以及它们如何引用定义在simple-weathersimple-persist中的其它bean,它们相应HTTP请求,让那些知道如何呈现Velocity模板的神奇的模板系统来控制输出。所有的魔法都在位于src/main/webapp/WEB-INF/weather-servlet.xml的Spring appliction context中配置。这个XML文件配置了控制器并引用了其它Spring管理的bean,它由ServletContextListener载入,后者同时也被配置从classpath中载入了applicationContext-weather.xmlapplicationContext-persist.xml。让我们仔细看一下???中展示的weather-servlet.xml

Example 7.17. weather-servlet.xml 中的 Spring 控制器配置

<beans>  
     <bean id="weatherController" 
           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"> 
                         <ref bean="weatherController" />
                    </entry>
                    <entry key="/history.x">
                         <ref bean="historyController" />
                    </entry>
               </map>
          </property>
     </bean>


     <bean id="velocityConfig" 
   class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
       <property name="resourceLoaderPath" value="/WEB-INF/vm/"/>
     </bean>

     <bean id="viewResolver" 
   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

weather-servlet.xml定义了两个控制器作为Spring管理的bean。weatherController有两个属性,引用weatherServiceweatherDAOhistoryController引用了weatherDAOlocationDAO bean。当ApplicationContext被创建的时候,它所处的环境能够访问simple-persistsimple-weather中定义的ApplicationContext。在???中你将看到如何配置Spring以归并多个Spring配置文件的组件。

2

urlMapping bean定义了调用WeatherControllerHistoryControllerURL模式。该例中,我们使用SimpleUrlHandlerMapping,将/weather.x映射到WeatherController,将/history.x映射到HistoryController

3

由于我们正使用Velocity模板引擎,我们需要传入一些配置选项。在velocityConfig bean中,我们告诉Velocity从/WEB-INF/vm目录中寻找所有的模板。

4

最后,viewResolver使用VelocityViewResolver类配置。Spring中有很多viewResolver实现,从用来呈现JSP或者JSTL页面的标准viewResolver,到用来呈现Freemarker模板的viewResolver。本例中,我们配置Velocity模板引擎,设置默认的前缀和后缀,它们将被自动附加到那些传给ModelAndView的模板名前后。

最后,simple-webapp项目中有一个web.xml,提供了这个web应用的基本配置。web.xml文件如???所示:

Example 7.18. simple-webapp 的 web.xml

<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> 
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:applicationContext-weather.xml
      classpath:applicationContext-persist.xml
    </param-value>
  </context-param>
  
  <context-param> 
    <param-name>log4jConfigLocation</param-name>
    <param-value>/WEB-INF/log4j.properties</param-value>
  </context-param>
  
  <listener> 
    <listener-class>
      org.springframework.web.util.Log4jConfigListener
    </listener-class>
  </listener>
  
  <listener>
    <listener-class> 
     org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  
  <servlet> 
    <servlet-name>weather</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping> 
    <servlet-name>weather</servlet-name>
    <url-pattern>*.x</url-pattern>
  </servlet-mapping>
</web-app>

1

这里有一些魔法能让我们在项目中重用applicationContext-weather.xmlapplicationContext-persist.xmlcontextConfigLocationContextLoaderListener用来创建一个ApplicationContext。当一个weather servlet被创建的时候,???中的weather-servlet.xml将由此contextConfigLocation中创建的ApplicationContext赋值。用这种方式,你可以在另外的项目中定义一组bean,然后可以通过classpath引用这些bean。由于simple-persistsimple-weatherJAR将会位于WEB-INF/lib,我们所要做的只是使用classpath:前缀来引用这些文件。(另一种选择是将所有这些文件拷贝到/WEB-INF,然后用过如/WEB-INF/applicationContext-persist.xml的方式引用它们)。

2

log4jConfigLocation用来告诉Log4JConfigListener哪里去寻找Log4J日志配置。该例中,我们告诉Log4J在/WEB-INF/log4j.properties中寻找。

3

这里确保当web应用启动的时候Log4J系统被配置。将Log4JConfigListener放在ContextLoaderListener前面十分重要;否则你可能丢失那些指向阻止应用启动问题的重要日志信息。如果你有一个特别大的Spring管理的bean集合,而其中一个碰巧在应用启动的时候出问题了,应用很可能会不能启动。如果你在Spring启动之前有了日志,你就有机会看到警告或错误信息。如果你在Spring启动之前没有配置日志,你就不知道为什么你的应用不能启动了。

4

ContextLoaderListener本质上是一个Spring容器。当应用启动的时候,这个监听器会根据contextConfigLocation参数构造一个ApplicationContext

5

我们定义一个名为weather的Spring MVC DispatcherServlet。这会让Spring从/WEB-INF/weather-servlet.xml寻找Spring配置文件。你可以拥有任意多的DispatcherServlet,一个DispatcherServlet可以包含一个或多个Spring MVC Controller实现。

6

所有以.x结尾的请求都会被路由至weather servlet。注意.x扩展名没有任何特殊的意义,这是一个随意的选择,你可以使用任意你喜欢的URL模式。