WEB开发网
开发学院软件开发Java Apache Geronimo 和 Spring 框架,第 6 部分: Spr... 阅读

Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术

 2010-04-16 00:00:00 来源:WEB开发网   
核心提示:开始之前本系列教程适合于需要了解 Spring 框架的更多信息以及如何在 Apache Geronimo 应用服务器上使用 Spring 框架的强大功能的 Java Platform、Enterprise Edition(Java EE)开发人员,关于本系列教程本系列教程共分为 6 个部分,Apache Geronim

开始之前

本系列教程适合于需要了解 Spring 框架的更多信息以及如何在 Apache Geronimo 应用服务器上使用 Spring 框架的强大功能的 Java Platform、Enterprise Edition(Java EE)开发人员。

关于本系列教程

本系列教程共分为 6 个部分,向您介绍了 Spring 框架及 Spring 框架怎样与 Geronimo 结合使用。我们将从检验各种 Spring 框架方法及其怎样与 Geronimo 服务器结合使用入手。在整个系列教程中,您将开发和部署个人电话本应用程序。该应用程序包括以下功能:

显示电话本

显示每个条目的详细信息

向电话本中添加一个新条目

编辑、修改和删除条目

向条目中添加更多详细信息,例如主电子邮件地址

第 1 部分 介绍了 Spring 框架的各个模块,并介绍了每个模块与在 Geronimo 应用服务器上开发 Java EE 应用程序的关系。该部分还说明了 Spring 框架所基于的方法。

第 2 部分 介绍了如何使用 Spring 框架在 Geronimo 上构建第一个骨架系统应用程序。

第 3 部分 中,您将采用通过 Apache Derby 数据库添加 Java 数据库连接 (JDBC) 支持来扩展在第 2 部分中获得的 Geronimo 应用程序。您还将了解如何将对象关系映射 (ORM) 集成到使用 iBatis 的应用程序中。

第 4 部分 中,您将面对 Spring AOP 和 Spring Web 框架。使用 Spring AOP,任何受 Spring 框架管理的对象都可变为面向方面的,并且本教程利用了通过 Spring AOP 提供的声明式事务管理服务。

第 5 部分 介绍了 Spring MVC。该教程向您介绍了 MVC 框架及 Web 视图,使您可以了解 Spring MVC 的入门知识。

在最后一部分,也就是第 6 部分中,介绍了如何通过 Spring 框架使用 JSP、Velocity、Tile 和 PDF 导出功能。您将使用和体验 Spring MVC 内置的各种 Web 视图。

关于本教程

如上所述,本系列教程的第 5 部分提供了对 Spring MVC 模块的完整介绍。您了解了 Spring MVC 所提供的各种控制器 —— MVC 中的 C。本教程将检验各种视图技术 —— MVC 中的 V。

Web 应用程序开发人员所面临的最大挑战之一是创建可适应的设计。使视图组件具有灵活性非常具有挑战性。由于 Spring 对视图的支持十分健壮,因此这种挑战较易管理。本教程中使用 JSP、Tile、Velocity 和 PDF 导出功能的目的在于演示 Spring MVC API 如何使这一切成为可能。

JSP 和 Velocity 是两项互补的视图技术。您可以用任意一项技术来创建视图,每项技术都有自己的优缺点。本教程演示了在样例电话本应用程序中用其中一项技术替代另一项技术有多么简单。

首先我们将查看 Spring MVC 中的 JSP 支持,然后查看 Tile,一种用于视图布局管理的优秀模板引擎。Tile 使您可以轻松地管理 Web 页面布局,而且 Spring 有对 Tile 的内置支持。您将使用这些类来更改电话本应用程序的 Web 布局。

接下来,您将通过使用 Velocity 模板引擎定义的视图来替代对 JSP 的使用。Velocity 使您可以轻松地访问视图中的 Java 对象,而无需复杂的定义和 Java 结构,例如 Try Catch 循环。

最后,您将看到如何调整电话本应用程序的主页使其显示为 PDF 文件。Spring 将处理创建 PDF 时所需的全部麻烦而复杂的代码和逻辑。它为您提供了一种干净的 API 来处理内容,而不必为 PDF 的细节而困惑。

先决条件

要继续学习本教程,您应当对以下内容有基本了解:

面向对象的编程

Java Platform,Enterprise Edition(Java EE)的术语

SQL 语句

XML 语义

JSP 标记、标记库和标记库描述符

了解 MVC 更佳,并且具有 Velocity 的工作知识也会非常有利,但并非强制要求。

系统要求

您的系统需要至少满足以下要求才能继续学习本系列教程:

The Spring Framework v1.2.8 —— 具有所有依赖性的压缩文件。

Apache Geronimo 1.1 —— Geronimo 是 Apache 的 Java 2 Platform, Enterprise Edition(J2EE)认证应用服务器。

Apache Derby 数据库 —— 本教程使用 Derby,该数据库是开源的轻量级数据库。Derby 是嵌入到 Geronimo 1.1 里的,因此不需要再单独安装。

Velocity JAR 文件 —— 您将需要来自 Velocity 模板引擎的 JAR 文件。您既可以从 Spring 框架安装中复制,也可以从 Velocity 站点下载。

iText —— 这是用于快速生成 PDF 的 PDF 库。Spring 将使用此库来生成 PDF,并且是 Spring 框架的一部分。

Struts —— 这是来自 Spring 的 Tile 支持,它依赖于 Struts API。您需要使用来自此 API 的主 struts.jar 文件,可以在 Spring 框架中找到。

Spring Tile 支持所需的常见库 JAR 文件 —— 您需要使用 commons-digester.jar、commons-collection.jar 和 commons-beanutils.jar。这些都来自 Spring 框架安装,因此只需将这些文件复制到开发环境中。

Standard taglib API —— 由于在 JSP 中使用的是 JSP 标准标记库(JSP Standard Tag Library,JSTL)标记,因此需要使用此压缩文件中的 JAR 文件。

标准的 JSTL 库 —— 当前版本为 1.1.2。

Apache Ant —— 确保正确配置 Ant 并且其 /bin 目录位于 Path 系统变量中。

Java 1.4.2 —— 确保 Java 安装并运行在系统中。

安装和配置软件

此部分包含安装和配置开发、部署和运行示例应用程序所必需的软件的说明。

安装 Spring 框架和 Geronimo:要使样例代码运行,需要安装运行 Geronimo 和 Spring 框架。(有关安装指南,请参阅本系列教程的 第 2 部分。)

解析 Tile 依赖性所需的 JAR 文件:Spring 中的 Tile 支持需要使用以下 JAR 文件:

Struts.jar —— <SPRING_HOME>\lib\struts

Commons-digester.jar —— <SPRING_HOME>\lib\jakarta-commons

Commons-collection-3.2.jar —— <SPRING_HOME>\lib\jakarta-commons

Commons-beanutils.jar —— <SPRING_HOME>\lib\jakarta-commons

所有这些 JAR 文件都随 Spring 框架安装包附带而来。您可以在以上列表中指出的目录中找到它们,.jar 文件名旁边。确保将所有这些 JAR 文件复制到 <WORKSPACE>/phonebook/lib 目录中。

解析 Velocity 依赖性所需的 JAR 文件:样例电话本应用程序需要包括 velocity-1.4.jar 才能使用 Velocity。Velocity 还需要使用复制的 commons-collections.jar 才能确保 Tile 完整性。Velocity .jar file 可以在 Spring 安装目录的 <SPRING_HOME>\lib\velocity 文件夹中找到。确保将此文件复制到 <WORKSPACE>/phonebook/lib 目录中。

解析 PDF 依赖性所需的 JAR 文件:Spring 的 PDF 支持 API 将使用 iText PDF 库,因此需要该库为应用程序创建 PDF。同其他 JAR 文件一样,此文件也是预打包在 Spring 安装中。您可以在 <SPRING_HOME>\lib\itext 中找到 itext-1.3.jar 文件。将此文件也复制到 lib 目录中。

安装 Apache 的 Standard Taglib 和 Spring taglib:您将使用在本系列教程的 第 5 部分 中定义的 JSP 并且使用它们扩展应用程序,因此需要安装 JSTL 库。(请参阅本系列教程的第 5 部分中的安装说明。)

应用程序的数据模型定义和数据库设置:您将使用在本系列教程的其它部分中创建的同一个 Derby 数据库。数据模型也是相同的。如果在第 3 部分、第 4 部分或第 5 部分中创建了数据库和表,则应当已经设置好了一切。如果未创建,请按照 第 3 部分 中的说明先完成设置。

Spring 所支持的各种视图技术简介

Spring 框架证明其威力的领域之一在于将视图技术与其余 MVC 框架及其功能分离,从而使开发人员可以非常迅速地更改视图技术。例如,如果希望使用 Velocity 替代 JSP,则调整一些配置文件是十分简单的事。在此部分中,您将看到 Geronimo 应用程序怎样可以从结合使用这些技术与 Spring 框架中获益。

JSP 支持

第 5 部分 演示了 Spring Framework 对 JSP 的扩展支持。Spring 提供了可以使用或扩展的开箱即用的控制器,这取决于应用程序的需求。Spring MVC 使您可以轻松地将精力集中在内容上,而无需担心实现和整合的详细信息。第 5 部分为您提供了大量示例。为 Web 页面中的操作定义了控制器,然后这些控制器将根据那些操作返回 JSP 视图。

在 Geronimo 应用程序中使用 JSP 而不使用 servlet 使您可以轻松地管理 Web 应用程序的内容。

Tile

Tile 是内置在 Apache Struts 框架中的模板引擎。Tile 提供的一些重要功能包括:

能够通过组合 Tile 创建屏幕/视图:页眉、页脚、菜单、主体等等。

可以将定义集中在 XML 文件中或直接集中到 JSP 页面。此示例使用了一个集中的 XML 文件用于视图定义。

能够继承定义和扩展另一个定义或覆盖参数。

能够使用 Tile 作为布局管理器甚至还可以为各种应用程序重用布局。

能够使用 Tile API 轻松地创建国际化的内容。

Spring MVC 提供了一个简单的框架用于将 Tile 集成到 Web 应用程序中。Tile 最重要的用途是作为 Web 布局管理器。Tile 简化了视图布局的更改过程,您无需更改任何 JSP 页面。本教程中的示例应用程序向您展示了如何定义布局并在各种页面中使用它。

如果 Geronimo 应用程序的视图在多个页面中都使用类似的视图组件,则使用 Tile 还能让您受益。例如,如果所有页面必须具有相同的页眉和页脚,甚至是具有相同的左键菜单,则可以将这些内容定义为 Tile 并且使用该 Tile API 作为布局管理器。

Velocity

Velocity,另一种基于 Java 技术的模板引擎,允许在视图中引用 Java 代码定义的对象。Velocity 的主要目标之一是提供一种更简单的视图技术作为 JSP 视图技术的备用方法,方法是尝试消除 JSP 标记和 Java 构造函数(如 Try Catch 循环)的复杂应用。

Velocity 还允许 Web 设计人员在 MVC 模型的基础之上与应用程序开发人员并行工作。Web 页面设计人员可以将全部精力集中在创建站点上,而编程人员可以将精力集中在应用程序代码上,因为 Velocity 将 Java 代码与 Web 页面分隔开来,使页面更具有可维护性。

图 1 显示了使用 Velocity 作为视图技术的基本 MVC 模型。

图 1. 提供了 MVC 中的 V 的 Velocity
Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术

视图被创建为 Velocity 模板或 .vm 文件。您可以将 Java 对象作为对这些视图的引用来传递。本教程的稍后部分中将介绍其工作方式。

干净可维护的 JSP 是使用 Velocity 作为 Geronimo 应用程序的视图技术的主要优点。Velocity 视图包含表示 Web 页面视图的 HTML,同时对 Java 对象的引用最少。

PDF 导出功能

为 Web 视图生成 PDF 对 Web 应用程序开发人员来说始终是个挑战。仅创建 PDF 就需要完成一系列繁琐的操作,而这甚至还没有考虑到添加内容时将遇到的困难。Spring MVC 的 PDF 支持很大程度上简化了该过程。它会处理创建 PDF 所涉及的所有复杂步骤,还提供了一个流线化的 API,以便将内容添加到其中。

Spring 的 PDF 支持使您可以轻松地在 Geronimo 应用程序中将页面视图导出为 PDF。

现在您已经打好了本教程涉及的各种技术的基础,是时候将它们集成到电话本应用程序中了。

扩展电话本应用程序来使用各种视图技术

在此部分中,首先概述如何将先几节中的视图技术集成到示例电话本应用程序中。随后讨论实际实现。

将各种视图技术集成到电话本应用程序中

集成 Tile 包括以下步骤:

为应用程序定义一种布局机制。这是一个简单的 Web 应用程序,因此是页眉 > 主体 > 页脚。

定义 Tile 模板文件。

为 Tile 模板定义视图解析程序。

将此模式扩展到电话本应用程序的所有页面。

集成 Velocity 包括以下步骤:

定义一个新控制器来处理 Velocity 模板请求。

为主页创建一个 Velocity 模板。

为 .vm 文件定义一个视图解析程序。

生成 PDF 包括以下步骤:

定义一个控制器来处理 PDF 视图请求。

定义一个视图类来为电话本主页创建 PDF。

将此添加到 Application Context 的视图解析程序链中。

工作区的目录结构

图 2 显示了应用程序的布局方法。按惯例下载本教程附带的源压缩文件(请参阅 下载 部分)并将其解压缩。

图 2. 解压缩源文件后的应用程序目录结构
Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术

如您在 图 2 中列出的目录结构中所见,没有用于 Tile 的新控制器 —— 一切全在于配置。在下面的部分中,您将看到 Spring 怎样使这一切成为可能。

学习目标

如果您到目前为止一直在阅读本系列教程,则应当一直在创建电话本应用程序。每期文章中都添加了更多功能。迄今为止,应用程序允许您:

显示电话本条目(默认情况下是主页)。

向电话本中添加条目。

删除选定条目。

修改选定电话本条目。

首先,您将把现有电话本应用程序转换为使用 Tile 作为布局管理器。首先将在电话本模板 JSP 中定义 Web 页面布局。Tile 视图定义文件将把特定视图的定义包含在此电话本模板中。

图 3 为您提供了一种使用 Tile 定义布局的方法。

图 3. 解压缩源文件后的应用程序目录结构
Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术

如您所见,Tile 用作布局管理器并且单独的实现页面都被视为 JSP Tile。布局管理器可以以任何一种您希望的方式替换 Tile,只要通过定义为模板元素提供实现页面。

接下来,看看如何仅通过更改 Application Context 文件中的某些配置来用 Velocity 中定义的视图替换 JSP。对于本教程,您将更改将被 Velocity 模板替换掉的主页。您可以使用它作为将其余页面转换为 Velocity 视图的练习。

应用程序的最后一部分向您展示了如何使用 Spring MVC 的 AbstractPdfView 类来创建 PDF 视图。此类将为您处理所有创建 PDF 文档的工作,并为您提供一个干净的文档用于添加内容。然后将手动创建电话本主页并以 PDF 形式返回。

注:Spring MVC 的 PDF 支持不同于 Web 页面的屏幕捕捉程序;还有其他工具可用于该目的。您必须手动创建需要导出到 PDF 文档中的视图。

将 Tile 集成到应用程序中

到目前为止,您的电话本应用程序一直是使用 JSP 开发的。查看 Tile 与应用程序协同工作的最佳方法是使用 Tile 作为布局管理器。下面几部分向您展示了该方法。

将 Tile 标记库添加到应用程序中

正如任何其他 JSP 标记库一样,必须将 Tile 库添加到 Web 应用程序部署描述符中,然后才能使用它。只需将清单 1 中的 taglib 元素添加到 web.xml 文件中。

清单 1. 在 web.xml 中添加 Tile 标记库

 <jsp-config> 
   <taglib> 
    <taglib-uri>/tiles</taglib-uri> 
    <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location> 
   </taglib> 
 </jsp-config> 

现在需要定义 Web 应用程序的布局。

在 Phonebook-layout.jsp 中定义一个模板布局

在 Tile 定义的上下文中,模板是使用 JSP 自定义标记库来描述页面布局的 JSP 页面。它定义了在没有指定内容的情况下应用程序页面的外观。内容将在运行时插入。正如您所见,它是一个简单的模板;将默认页面结构定义为页眉 > 主体 > 页脚。清单 2 显示了此 JSP 的代码。

清单 2. phonebook-layout.jsp 显示 Web 页面的布局

<%@ taglib prefix="tiles" uri="/tiles" %> 
 
<!-- This page defines the general layout for Phonebook Application Pages --> 
 
  <tiles:insert name="header"/> 
  <tiles:insert name="body"/> 
  <tiles:insert name="footer"/> 

您将为所有页面应用此模板。它将使用 tiles:insert 标记来插入按名称属性表示的内容。正如此处所示,没有内容,只有页面的布局。

现在需要把视图与 Tile 视图名称关联起来,这是接下来您要做的工作。

配置 Tile 使其与 Spring 协同工作

为了能够将 Tile 与 Spring MVC 结合使用,必须使用包含在视图定义中的文件对其进行配置。在 Spring 中,使用 TilesConfigurer 类来完成此操作。清单 3 显示了它是如何在 Application Context 文件中定义的。

清单 3. 视图定义文件名被传递给 Application Context 中的 TilesConfigurer

<!-View Resolver for Tiles --> 
   
  <bean id="tilesViewResolver" 
class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> 
    <property name="order" value="1" /> 
    <property name="basename" value="views-phonebook-tiles"/> 
  </bean> 
   
  <!-- Tiles configurer reads the view definition from the XML file --> 
  <bean id="tilesConfigurer" 
class="org.springframework.web.servlet.view.tiles.TilesConfigurer"> 
    <property name="factoryClass" 
value="org.apache.struts.tiles.xmlDefinition.I18nFactorySet"/>   
    <property name="definitions"> 
      <list> 
        <value>/WEB-INF/defs/phonebook-definitions.xml</value> 
      </list> 
    </property> 
  </bean> 

您可以看到视图定义文件的名称为 phonebook-definitions.xml。TilesConfigurer 将在初始化时读取此文件,然后使可用的定义文件中的所有视图都可在应用程序中使用。

另一个 bean 定义是用于 ViewResolver 的,Spring 必须使用它才能解析视图。ResourceBundleViewResolver 是 Spring 框架中最常用的视图解析程序之一。您需要传递到定义映射到类和 URL 的视图名的属性文件中。清单 4 显示了 views-phonebook-tiles.properties 文件中的内容。

清单 4. views-phonebook-tiles.properties 文件中的视图定义

home-mvc.class=org.springframework.web.servlet.view.tiles.TilesView 
home-mvc.url=home-mvc 
 
addentry-mvc.class=org.springframework.web.servlet.view.tiles.TilesView 
addentry-mvc.url=addentry-mvc 
 
modifyentry-mvc.class=org.springframework.web.servlet.view.tiles.TilesView 
modifyentry-mvc.url=modifyentry-mvc 

清单 4 中的定义告诉 Spring MVC 模块应用程序中有三个视图,并且每一个视图都是一个 TilesView。第二行显示特定于表示这些视图的每个页面的 URL。

接下来,您将看到如何将由特定 JSP 实现的实际视图插入到 Tile 布局中。

将视图与实际 JSP 实现关联起来

清单 2 中所示的布局 Tile 是通用的并将被所有 Web 页面使用。它不知道关于主页、addEntry 或 ModifyEntry JSP 页面内容的任何信息。就是这样设计的,因为它允许您对很多页面重用此布局。不是将内容硬编码到布局定义页面中,而是在运行时将其作为参数传递到布局页面中。

在 Spring 中,通过定义 XML 定义文件来完成这项工作。清单 5 显示了在电话本应用程序中这是怎样完成的。

清单 5. 用于在运行时将内容传递到布局页面的定义文件

<tiles-definitions> 
 
   <!-- DEFAULT MAIN TEMPLATE --> 
   <definition name="template" 
page="/WEB-INF/jsp/phonebook-layout.jsp"> 
      <put name="header" 
 value="/WEB-INF/jsp/header.jsp"/> 
      <put name="footer" 
value="/WEB-INF/jsp/footer.jsp"/> 
   </definition> 
     
    <!-- The Home Page --> 
    <definition name="home-mvc" extends="template"> 
      <put name="body" value="/WEB-INF/jsp/home-mvc.jsp" 
type="page"/> 
 
   </definition> 
     
    <!-- The Add Entry Page --> 
    <definition name="addentry-mvc" extends="template"> 
      <put name="body" value="/WEB-INF/jsp/addentry-mvc.jsp" 
type="page"/> 
 
   </definition> 
 
   <!-- The Modify Entry Page --> 
   <definition name="modifyentry-mvc" extends="template"> 
      <put name="body" value="/WEB-INF/jsp/modifyentry-mvc.jsp" 
type="page"/>  
   </definition> 
     
     
</tiles-definitions> 

第一个定义通过为模板提供名称和包含该模板内容的页面来定义默认模板。如果返回至 清单 2(在 phonebook-layout.jsp 中为应用程序定义布局的位置),则会注意到插入到该布局中的视图的名称与在此模板中定义的那些视图名称相同。因此本质上是将内容与这里的这些定义中的模板关联了起来。

模板定义中的 put 元素将告诉 Tile 框架获取视图的位置。在本例中,页眉视图将从 header.jsp 中获取其内容,而页脚将从 footer.jsp 中获取其内容。

下一个 Tile 定义将定义主页。它将扩展默认模板并简单地将主体插入 home-mvc.jsp 页面中。如果仔细查看此定义,您将看到如何构建主页。以下是详细操作过程:

由于 home-mvc 扩展了默认模板,因此它将自动从电话本布局中获取页眉和页脚内容。

此定义将通过指向 home-mvc.jsp 页面把主体置入主页中。

Spring MVC 框架在运行时将把所有 JSP 收集到一起并将内容作为一个单页面传递回给客户机。这是一种干净而简单的方法来编写 Web 应用程序。

构建并运行它

查看运行的最简单且最快速的方法是使用 Geronimo Web 控制台部署 phonebook.war 文件。您可以按照 第 2 部分 中的说明部署 .war 文件。部署了 .war 文件后,请将浏览器指向 http://localhost:8080/phonebook/home-mvc.act 以查看运行中的启用了 Tile 的应用程序。图 4 显示了执行它之后主页的外观。

图 4. 浏览器中启用了 Tile 的主页
Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术

现在您已经看到了 Tile 与 Spring 框架在 Geronimo 应用服务器上协同工作,您可以继续将 Velocity 集成到应用程序中。

将 Velocity 集成到应用程序中

此部分将展示如何将 JSP 替换为使用 Velocity 定义的视图 —— 也称为 Velocity 视图或 .vm 文件。您将了解到通过仅对配置文件做出更改就可以更改视图技术是多么简单。Web 应用程序必须包括 velocity.x.jar 文件才能使用 Velocity (请参阅 先决条件),因此请确保已经将该 .jar 文件复制到了 WEB-INF/lib 目录中。

将 Velocity 配置到 Spring 的 Application Context 中

有两种方法可以让 ApplicationContext 知道 Velocity 模板的存在。第一种方法是使用 VelocityConfigurer。您需要做的工作就是在 resourceLoaderPath 中提供 Velocity 模板的位置,VelocityConfigurer 将在运行时使来自该位置的所有模板都可用于应用程序。

另一种方法(且为首选方法)是定义自己的控制器,然后使用该控制器来定位视图。清单 6 显示了 phonebook-servlet.xml 文件中的这些 bean 定义。

头两个 bean 定义定义了对 home.vel 页面的请求应当由 PhonebookVelocityController 类来控制。您将在以后的部分中定义此类。第三个 bean 定义使用了 VelocityViewResolver 来解析带有 .vm 扩展名的所有页面请求。

清单 6. 让上下文知道 Velocity 视图的存在的 Bean 定义

<bean id="urlMapping" 
 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 
    <property name="mappings"> 
      <props> 
          <prop key="/*.do">phonebookController</prop> 
          <prop key="/*.htm">phonebookFlowController</prop> 
          <prop 
key="/*.flow">phonebookFlowController</prop> 
          <prop 
key="/addentry-mvc.act">addEntryFormController</prop> 
          <prop 
key="/home-mvc.act">phonebookHomeController</prop> 
          <prop 
key="/modifyentry-mvc.act">modifyEntryFormController</prop> 
          <prop 
key="/deleteentry-mvc.act">deleteEntryFormController</prop> 
          <prop 
key="/home.vel">phonebookVelocityController</prop> 
          <prop 
key="/home.pdf">phonebookPDFController</prop> 
      </props> 
    </property> 
  </bean> 
 
  <bean id="phonebookVelocityController" 
class="phonebook.velocity.PhonebookVelocityController"/> 
 
  <!-- View Resolver for Velocity --> 
   
  <bean id="velocityViewResolver" 
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver" 
> 
    <property name="cache" value="true"/> 
    <property name="prefix" value=""/> 
    <property name="suffix" value=".vm"/> 
  </bean>  

定义 Velocity 控制器类

为什么要首先为 Velocity 定义一个控制器?记住您要用从数据库中读取的所有电话本条目填充主页,并且从数据库中读取此列表并把它作为一个对象传递给 Velocity 视图。Velocity 引擎将把此对象传递给主页模板,该模板随后将使用 Velocity 模板语言标记迭代此对象。清单 7 显示了 PhonebookVelocityController 的代码。

清单 7. 从数据库中读取 PhonebookEntries 并传递给视图的类

public class PhonebookVelocityController implements Controller { 
 
  /** Creates a new instance of PhVelocityController */ 
  public PhonebookVelocityController() { 
  } 
 
  public ModelAndView handleRequest(HttpServletRequest request, 
HttpServletResponse response) 
  throws ServletException, IOException, Exception  { 
    ModelAndView view = null; 
    if (request.getRequestURI().indexOf("home")!=-1) { 
 
      view = new ModelAndView("phonebook-template"); 
      view.addObject("viewId", "home"); 
      view.addObject("time", new Date() ); 
 
      WebApplicationContext ctx = 
WebApplicationContextUtils.getWebApplicationContext(request.getSession(). 
getServletContext()); 
      IPhonebookDataProvider pb = (IPhonebookDataProvider) 
ctx.getBean("phonebook"); 
      List phonebookEntries = pb.getPhonebookEntries(); 
      System.out.println("Velocity No. of Entries = 
 "+phonebookEntries.size()); 
 
      view.addObject("phEntries", phonebookEntries); 
 
    } 
    return view; 
  } 
} 

您可以看到控制器使用 WebApplicationContext 来获取 phonebook bean 并从数据库中读取电话本条目。然后它将此作为一个对象添加到视图中。您接下来将看到 Velocity 模板怎样读取它。

为主页视图创建一个 Velocity 模板

如同您为 Tile 视图所做的工作一样,需要在模板文件中定义默认布局。清单 8 显示了 phonebook-template.vm。

清单 8. 定义 Web 应用程序布局的默认模板

#set ($viewName = ${viewId}) 
#set ($viewName = "${viewName}.vm") ## View name is passed in dynamically from controller. 
 
#parse("header.vm") 
#parse($viewName) 
#parse("footer.vm") 

您可以看到电话本布局是一样的。Velocity 引擎在运行时读取此模板,并且解析标记将呈现作为参数传递进来的组件。viewName 是由 phonebookVelocityController 在运行时动态传递的。

清单 9 显示了主页的模板代码。

清单 9. 基于 Velocity 的主页的模板代码

#set ($time1 = ${time}) 
#set ($phonebook = ${phEntries}) 
 
<html> 
 
<script type="text/javascript"> 
function goToAddEntryPage() { 
  document.myForm.action="/phonebook/addentry.vel"; 
  document.myForm.method="GET"; 
  document.myForm.submit(); 
} 
 
function setId(entryID, rowID) { 
  document.myForm.entryID.value = entryID; 
  document.myForm.rowID.value = rowID; 
} 
 
function noRowSelected() { 
  var entryID = document.myForm.entryID.value; 
  var rowID = document.myForm.rowID.value; 
   
  if (entryID == "" || rowID == "")  { 
    return "true"; 
  }  else  { 
    return "false"; 
  } 
} 
 
function goToModifyEntryPage() { 
  if (noRowSelected() == "true")  { 
    alert("Please select an Entry to Modify"); 
    return; 
  } 
   
  document.myForm.action="/phonebook/modifyentry-mvc.act"; 
  document.myForm.method="GET"; 
  document.myForm.submit(); 
} 
 
function deleteEntry() { 
  if (noRowSelected() == "true")  { 
    alert("Please select an Entry to Delete"); 
    return; 
  }  else   { 
    var retVal = confirm("Please click OK to confirm your deletion. Click 
Cancel otherwise"); 
    if (retVal != true)    { 
      return; 
    } 
  } 
  document.myForm.action="/phonebook/deleteentry-mvc.act"; 
  document.myForm.method="POST"; 
  document.myForm.submit(); 
} 
 
</script> 
 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
    <title>Phone Book - $time1 </title> 
  </head> 
  <body> 
 
    <!--The Heading of the Phone Book Page--> 
    <h1 align=center>Phone Book</h1> 
    
    <BR> 
    <BR> 
    <!-- The table containing phone book contents. --> 
 
    <TABLE border="1" width="100%"> 
         
      <TH width="5%" align=center>Select</TH> 
      <TH width="25%" align=center>Name</TH> 
      <TH width="15%" align=center>Home Phone</TH> 
      <TH width="15%" align=center>Work Phone</TH> 
      <TH width="15%" align=center>Cell Phone</TH> 
      <TH width="25%" align=center>Email</TH> 
         
  
      #foreach( $pbEntry in $phonebook ) 
      #set ($i = $velocityCount - 1) 
      <TR> 
        <TD align=center><input type=radio name="data_"$i 
alt="Select to Delete" align="middle"></TD> 
        <TD align=center>$pbEntry.getFirstName() 
$pbEntry.getLastName()</TD> 
        <TD align=center>$pbEntry.getHomeNumber()</TD> 
        <TD align=center>$pbEntry.getWorkNumber()</TD> 
        <TD align=center>$pbEntry.getCellNumber()</TD> 
        <TD align=center>$pbEntry.getEmail()</TD> 
      </TR> 
      #end 
    </TABLE> 
 
    <BR> 
    <BR> 
     
    <table align=center>    
      <!-- The row containing command buttons --> 
      <TR align=center> 
       <!-- 
       <TD><input type=submit name="Add" value="Add an Entry" 
></TD> 
       <TD><input type=button name="Modify" value="Modify 
Selected Entry" 
></TD> 
       <TD><input type=button name="Delete" value="Delete 
Selected Entry" 
></TD> 
       --> 
      </TR> 
    </table> 
  </body> 
  <BR><BR><BR><BR><BR><BR> 
   
</html> 

这个 home.vm 模板定义使用了 Velocity 的模板语言(Velocity Template Language,VTL)。此模板的第二行将获取由 Velocity 控制器类传入的 pbEntries 对象。然后用它迭代用电话本条目填充表的列表。

注:本教程并不详细介绍 VTL。

此过程中的最后一步是为 .vm 请求添加 URL 映射。

为 *.vm 请求添加 URL 映射

现在您应当很熟悉清单 10 中所示的 servlet 映射。

清单 10. *.vm 请求的 URL 映射

 <servlet-mapping> 
  <servlet-name>phonebook</servlet-name> 
  <url-pattern>*.vel</url-pattern> 
 </servlet-mapping> 

这就是您需要做的全部操作。现在让我们来看看它的运行情况!

构建并部署它

像以前一样,查看运行的最简单且最快速的方法是使用 Geronimo Web 控制台来部署本教程附带的示例软件包中的 phonebook.war。然后将浏览器指向 http://localhost:8080/phonebook/home.vm 以查看运行中的 Velocity 引擎。

如果一切运行正常,则您的浏览器页面将如图 5 所示。

图 5. 显示运行中的 Velocity 引擎的主页
Apache Geronimo 和 Spring 框架,第 6 部分: Spring MVC:使用 Web 视图技术

下一个部分将向您展示使用 Spring MVC 的 PDF 支持将 Web 页面的内容返回为一个 PDF 是多么简单。

将文档视图导出为 PDF

HTML 页面并不总是查看模型数据的最佳方法,因此 Spring 使您可以简单地生成 PDF 文档。此部分将向您展示如何将一直使用的同一个模型呈现为一个 PDF 文档并将其以正确的内容类型返回给客户机。

首先必须定义一个控制器以从数据库表中获取电话本条目的列表并将其传递给 PDF 视图类,从而开始开发 PDF 控制器。

定义 PhonebookPDFController 类

此类将使用 Spring ApplicationContext 的 bean 定义读取 phonebookEntries 类,然后将其传递给 PhonebookPdfView 类所表示的 PDF 视图。清单 11 显示了此控制器的代码。






清单 11. PhonebookPDFController 类读取电话本条目并将其传递给 PhonebookPdfView 类

public class PhonebookPDFController extends 
MultiActionController { 
 
  private static final String PDF_VIEW = "phonebook_pdfView"; 
 
  private String pdfView = PDF_VIEW; 
 
  /** Creates a new instance of PbPDFController */ 
  public PhonebookPDFController() { 
  } 
 
 
  public void setPdfView(String view) { 
   this.pdfView = view; 
  } 
 
  /** 
   * Custom handler for phonebook PDF document. 
   * @param request current HTTP request 
   * @param response current HTTP response 
   * @return a ModelAndView to render the response 
   */ 
  public ModelAndView handlePdf(HttpServletRequest request, 
HttpServletResponse response) throws ServletException, Exception { 
 
    WebApplicationContext ctx = 
WebApplicationContextUtils.getWebApplicationContext(request.getSession(). 
getServletContext()); 
    IPhonebookDataProvider pb = (IPhonebookDataProvider) 
 ctx.getBean("phonebook"); 
    List phonebookEntries = pb.getPhonebookEntries(); 
 
    return new ModelAndView(this.pdfView, "phonebook", 
phonebookEntries); 
  } 
 
 
} 

下一个逻辑步骤是定义 View 类,该类将通过模型输出实际生成 PDF。

定义 PDF View 类

PhonebookPdfView 是 AbstractPdfView 类的子类,用于实现特定于 Web 视图的 PDF 文档的自定义生成。在此类中最值得注意的方法是 buildPdfDocument。这是所有魔术变幻的关键位置所在。清单 12 显示了为电话本应用程序生成 PDF 的完整代码。

清单 12. PDF 视图类将创建 PDF 文档

public class PhonebookPdfView extends AbstractPdfView { 
 
  private static final Font HEADLINE_FONT = new Font( Font.TIMES_ROMAN , 18, 
Font.BOLD, Color.black ); 
  private static final Font DATA_HEAD_FONT = new Font( Font.TIMES_ROMAN, 10, 
Font.BOLD, Color.black ); 
  private static final Font TEXT_FONT = new Font( Font.TIMES_ROMAN, 8, 
 Font.NORMAL, 
Color.black ); 
  private static final Font FOOTER_FONT = new Font( Font.TIMES_ROMAN, 5, 
 Font.NORMAL, 
Color.black ); 
  private static final int NO_OF_COLUMNS = 5; 
 
 
  /** Creates a new instance of PhonebookPdfView */ 
  public PhonebookPdfView() { 
  } 
 
  protected void buildPdfMetadata(Map model, Document document, 
HttpServletRequest request) { 
    document.addTitle("Phone Book"); 
    document.addCreator("Arun Chhatpar"); 
  } 
 
 
  protected void buildPdfDocument( 
      Map model, 
      Document doc, 
      PdfWriter writer, 
      HttpServletRequest req, 
      HttpServletResponse resp) 
      throws Exception { 
 
    List phonebook = (List) model.get("phonebook"); 
 
    String title = "Phone Book"; 
    Paragraph h1 = new Paragraph(title, HEADLINE_FONT); 
    h1.setAlignment(Paragraph.ALIGN_CENTER); 
    doc.add(h1); 
    doc.add(new Paragraph(" ")); 
    doc.add(new Paragraph(" ")); 
    doc.add(new Paragraph(" ")); 
 
    // We create a table for used criteria and extracting information 
    PdfPTable table = new PdfPTable(NO_OF_COLUMNS); 
    int headerwidths[] = {20, 20, 20, 20, 20}; 
    table.setWidths(headerwidths); 
    table.setWidthPercentage(100); 
    table.getDefaultCell().setBorderWidth(1); 
    table.getDefaultCell().setBorderColor(Color.black); 
    table.getDefaultCell().setPadding(3); 
    table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER); 
    table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE); 
 
    table.addCell(new Phrase("Name", DATA_HEAD_FONT)); 
    table.addCell(new Phrase("Home Phone", DATA_HEAD_FONT)); 
    table.addCell(new Phrase("Work Phone", DATA_HEAD_FONT)); 
    table.addCell(new Phrase("Cell Phone", DATA_HEAD_FONT)); 
    table.addCell(new Phrase("Email", DATA_HEAD_FONT)); 
 
    // We set the above row as title 
    // and adjust properties for normal cells 
    table.setHeaderRows(1); 
    table.getDefaultCell().setBorderWidth(1); 
 
    // We iterate now on the Phonebook list 
    Iterator it = phonebook.iterator(); 
    while(it.hasNext()) { 
      PhonebookEntry pEntry = (PhonebookEntry) it.next(); 
      table.addCell(new Phrase(pEntry.getFirstName() + " " + 
 pEntry.getLastName(), 
TEXT_FONT)); 
      table.addCell(new Phrase(pEntry.getHomeNumber(), TEXT_FONT)); 
      table.addCell(new Phrase(pEntry.getWorkNumber(), TEXT_FONT)); 
      table.addCell(new Phrase(pEntry.getCellNumber(), TEXT_FONT)); 
      table.addCell(new Phrase(pEntry.getEmail(), TEXT_FONT)); 
    } 
    doc.add(table); 
 
    doc.add(new Paragraph(" ")); 
    doc.add(new Paragraph(" ")); 
    doc.add(new Paragraph(" ")); 
    doc.add(new Paragraph(" ")); 
 
 
    String footerStr1 = "This example is developed to demonstrate the Spring 
framework on the Geronimo Application Server."; 
    String footerStr2 = "This example is designed purely for 
demonstrating the capabilities of the framework. It should under no 
circumstances be used for production use."; 
    Paragraph footer1 = new Paragraph(footerStr1, FOOTER_FONT); 
    footer1.setAlignment(Paragraph.ALIGN_CENTER); 
    doc.add(footer1); 
    Paragraph footer2 = new Paragraph(footerStr2, FOOTER_FONT); 
    footer2.setAlignment(Paragraph.ALIGN_CENTER); 
    doc.add(footer2); 
 
  } 
} 

正如您所见,必须手动为 PDF 文档创建内容。但是 Spring 的支持类使这一切变得更加简单!

下一步是为 PDF 视图定义视图解析程序。

PDF 视图的视图解析程序

PDF 视图的视图解析程序类似于 Velocity 的视图解析程序定义。您现在可能注意到了使用的是用于 PDF 控制器类的 MultiActionController。这样做是希望向您展示另一种处理和解析 Spring 中的视图的方法。清单 13 对您来说应当很熟悉。

清单 13. 在 ApplicationContext 中添加 PDF 控制器和解析程序

  <bean id="urlMapping" 
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 
    <property name="mappings"> 
      <props> 
          <prop key="/*.do">phonebookController</prop> 
          <prop key="/*.htm">phonebookFlowController</prop> 
          <prop 
key="/*.flow">phonebookFlowController</prop> 
          <prop 
key="/addentry-mvc.act">addEntryFormController</prop> 
          <prop 
key="/home-mvc.act">phonebookHomeController</prop> 
          <prop 
key="/modifyentry-mvc.act">modifyEntryFormController</prop> 
          <prop 
key="/deleteentry-mvc.act">deleteEntryFormController</prop> 
          <prop 
key="/home.vel">phonebookVelocityController</prop> 
          <prop 
key="/home.pdf">phonebookPDFController</prop> 
      </props> 
    </property> 
  </bean> 
 
 
 <bean id="phonebookPDFController" 
class="phonebook.pdf.PhonebookPDFController"> 
    <property name="methodNameResolver"> 
      <bean 
class="org.springframework.web.servlet.mvc.multiaction. 
PropertiesMethodNameResolver"> 
        <property name="mappings"> 
          <props> 
            <prop 
 key="/home.pdf">handlePdf</prop> 
          </props> 
        </property> 
      </bean> 
    </property> 
  </bean> 
   
 
<!-- View Resolver for PDF --> 
  <bean id="pdfViewResolver" 
class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> 
    <property name="order" value="2" /> 
    <property name="basename" value="views-phonebook-pdf"/> 
  </bean> 

像以前一样,ResourceBundleViewResolver 需要用于类的属性文件和用于视图的 URL 定义。在本例中,views-phonebook-pdf.properties 文件只包含一行:phonebook_pdfView.(class)=phonebook.pdf.PhonebookPdfView。

如果查看控制器类,它所使用的视图与此属性类中定义的一样。这是 Spring 把此视图类与该控制器关联起来的方法。

最后一步是为 PDF 请求添加 URL 映射。

为 PDF 请求添加 URL 映射

清单 14 向您展示了如何将 URL 映射添加到 web.xml 文件中以处理 *.pdf 请求。

清单 14. web.xml 文件中用于 *.pdf 的 URL 映射

 <servlet-mapping> 
  <servlet-name>phonebook</servlet-name> 
  <url-pattern>*.pdf</url-pattern> 
 </servlet-mapping> 

现在是时候构建、部署和运行应用程序以便查看其运行情况。

构建、部署和运行它

本教程附带的源压缩文件包括所有类、配置文件和 Ant 构建文件(如果需要构建它)。本教程源文件还有一个可部署的 .war 文件,其中包含所需的一切内容。您可以使用任意一种方法来获取 phonebook.war 文件。

还需要确保 readme.txt 文件中所提及的所有 JAR 文件都在 <WORKSPACE>/phonebook/lib 目录中。请仔细阅读该文件中的说明,并确保将所有必需的文件都复制到 <WORKSPACE>/phonebook/lib 中。注:有关构建和解压缩这些文件的更多信息,您可以参阅本系列教程的 第 2 部分 中的构建和打包说明。

使用 Geronimo 中的 Deploy New 工具部署 phonebook.war。如果一切运行正常,您将在 Geronimo Web 控制台上看到一条消息,显示 Phonebook application deployed successfully。接下来,将浏览器指向新页面:http://localhost:8080/phonebook/home.pdf。如果一切运行正常,您应当会在浏览器中看到用 Adobe Acrobat Reader 打开的 PDF。

Spring 框架的优点

您已经在本教程中了解了 Spring 框架所支持的各种视图技术。大多数 Web 开发人员凭经验都知道将 Web 内容和布局模块化有多么重要。虽然 JSP 是一种有用的技术,但是它有一些固有的缺点,例如缺少布局或布局管理器的本地支持。模板技术(如 Tile)填补了该空白。Tile 提供了一种优秀的方法在单个布局文件中定义 Web 应用程序的布局,该布局文件还使您可以轻松地根据业务需要进行更改。下面是一些 Spring 对这些技术的支持所提供的优点:

明确分隔视图与进入这些视图中的内容

能够分隔任务(使用 Spring 的模板使应用程序开发人员可以轻松地将精力完全集中到应用程序代码上,同时使 Web 开发人员可以设计最佳的 HTML 页面而无需担心如何呈现页面)

能够动态改变视图技术

能够测试单个视图组件并且在开发周期早期就能准确指出错误

结束语

本教程向您详细介绍了 Spring MVC 所支持的各种视图技术。您看到了在样例电话本应用程序中实现这些技术有多么简单。Spring 的分层架构允许您根据应用程序的需要来引入恰到好处的功能,并且允许您添加更多您熟悉的特殊技术。

使用模板定义和管理视图的布局并不新鲜,但是将此技术与 Spring 结合使用十分简单。您已经看到,Spring 证明了自己是一种强大的框架,可以为您处理很多复杂的问题,同时还提供了一种简单的方法来执行必要的任务(例如创建 PDF)。

下载

描述名字大小下载方法
第 6 部分的源代码geronimo.spring6.source.zip126KBHTTP
第 6 部分的 WAR 文件geronimo.spring6.war.zip7,149KBHTTP

Tags:Apache Geronimo Spring

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接