内容


Apache Geronimo 和 Spring 框架,第 5 部分

Spring MVC

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Apache Geronimo 和 Spring 框架,第 5 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Apache Geronimo 和 Spring 框架,第 5 部分

敬请期待该系列的后续内容。

开始之前

本系列教程适合于需要了解 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 部分 展示了如何通过 Derby 数据库添加 Java Database Connectivity (JDBC) 支持来扩展在第 2 部分中获得的 Geronimo 应用程序。您还了解了如何将对象关系映射(Object Relational Mapping,ORM)集成到使用 iBATIS 的应用程序中。

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

此部分是第 5 部分,介绍了 Spring Model-View-Controller (MVC)。该教程向您介绍了 Spring MVC 框架及 Web 视图,使您可以了解 Spring MVC 的入门知识。

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

关于本教程

在本教程中,您将深入了解 Spring MVC 的详细信息并扩展电话本样例应用程序使其具有更多功能 —— 特别是修改和删除功能。本教程将向您展示如何使用丰富的 Spring MVC API 集中的一些最有用的类,并且将定义控制器来处理 JSP 页面的操作。

您还将了解如何扩展和使用 Spring MVC 提供的数据验证类。您将使用标准标记库以及 Spring 数据绑定将数据对象绑定到将要创建的 JSP 视图。Standard Taglib 已被选定,它将实现 1.1 版的 JSP 标准标记库 (JSTL),因为使用 JSTL 库可以轻松地实现 JSP 中所需的迭代操作。JSTL 是 Sun Microsystems 提供的一个易于使用的 JSP 标记库,它封装了 Web 应用程序所必需的大多数常见功能,例如迭代和条件语句(注:您可以选择不使用 JSTL,但 JSP 将不会像本教程中所示的这些 JSP 这样干净和模块化)。

先决条件

本教程假定您熟悉面向对象的编程 (OOP) 并且熟知 Java 2 Platform, Enterprise Edition (J2EE)、Java EE 术语和基本的 MVC 概念。由于本教程的目的在于了解 Spring MVC,因此只简要讨论了大多数基本的 MVC 概念。

系统要求

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

  • The Spring Framework v1.2.8 —— 具有所有依赖性的压缩文件。
  • Apache Geronimo 1.1 —— Geronimo 是 Apache 的 J2EE 认证应用服务器。
  • Standard Taglib API —— 您将在 JSP 中使用 JSTL 标记,并且需要使用本教程末尾的 下载 部分中附带的压缩文件中的 .jar 文件。
  • 标准 JSTL 库 —— 当前版本为 1.1.2。
  • Apache Derby 数据库 —— 本教程使用 Derby,该数据库是开源的轻量级数据库。Derby 是嵌入到 Geronimo 1.1 里的,因此不需要再单独安装。
  • Apache Ant —— 确保正确配置 Ant 并且其 /bin 目录位于 Path 系统变量中。
  • Java 1.4.2 —— 确保 Java 安装并运行在系统中。

安装和配置软件

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

  1. 安装 Spring 框架和 Geronimo。要使样例代码运行,需要安装运行 Apache Geronimo 和 Spring 框架。有关详细的安装说明,请返回至 第 2 部分
  2. 安装 Apache 和 Spring taglib 的 Standard Taglib。JSP 主页将动态读取数据并将其填充到一张表中。您将了解如何使用一些来自 JSTL 和 Spring 标记库的标记使 JSP 页面变得更干净。将主要使用 JSTL 来消除主页中的循环 Java 代码。要开发的应用程序将主要使用 Spring 标记库把表单与命令对象绑定在一起。
  3. 下载标准 JSTL 库。当前版本是 1.1.2,并且将使用此版本用于您的应用程序。它实现 Sun 的 JSTL 1.1 规范。下载如上所示的压缩文件(标准 JSTL 库链接),并且将其保存到硬盘驱动器中的某个位置。下载文件后,将其解压缩到应用程序所在的驱动器中。(例如,我把它安装到 K: 驱动器中。)解压缩文件应当会创建名为 jakarta-taglibs-standard-1.1.2 的目录。(在我的驱动器中,此安装位于 K:\ jakarta-taglibs-standard-1.1.2 目录中。)解压缩后,将 jstl.jar 和 standard.jar 文件复制到 <WORKSPACE>/phonebook/lib 目录中。Spring 标记库已包含在 Spring 发布版中。
  4. 复制标记库描述符(Tag Library Descriptor,TLD)。将 Standard Taglib 目录中的 c.tld 以及 Spring 框架发布版目录中的 spring.tld 复制到 WEB-INF 文件夹中。您可以从刚复制到库目录中的 .jar 文件中提取标记库描述符(Tag Library Descriptor,TLD)。
  5. Apache Derby 数据库。预打包在 Geronimo 1.1 中的 Derby 数据库安装无需任何特殊配置。如果您已经在本系列教程的 第 3 部分第 4 部分 中创建了数据库和表,则无需做任何操作。如果尚未这样做,请按照第 3 部分中关于创建数据库和表的说明进行操作。
  6. 为应用程序定义数据模型和设置数据库。您将使用在本系列教程的第 3 部分中创建的数据库。数据模型也是一样的。如果您已经按照本系列教程执行过操作,那么所有的部分都应该设置好了。如果没有,则应当按照 第 3 部分 中的说明进行操作,然后再尝试部属此应用程序。

Spring MVC 简介

在此部分中,您将简要了解将 MVC 设计模式用于 Web 应用程序的优点。然后了解 Spring 框架的 MVC 实现。

为什么使用 MVC?

很多应用程序的问题在于处理业务数据和显示业务数据的视图的对象之间存在紧密耦合。通常,更新业务对象的命令都是从视图本身发起的,使视图对任何业务对象更改都有高度敏感性。而且,当多个视图依赖于同一个业务对象时是没有灵活性的。让我们来研究一下 MVC 如何解决这些问题。

MVC 作为设计模型

MVC 是一种著名的设计模式,特别是在 Web 应用程序领域。模式全都是关于将包含业务数据的模块与显示模块的视图解耦的。这是怎样发生的?视图(例如,JSP 页面)怎样能够与其模型(例如,包含数据的 JavaBean)解耦?记得这句格言么?一个层次的重定向几乎可以解决计算机业中的所有问题。确实,在模型和视图之间引入重定向层可以解决问题。此重定向层是控制器。控制器将接收请求,执行更新模型的操作,然后通知视图关于模型更改的消息。依赖于模型的状态并且依赖于请求的控制器可以决定要显示哪个视图。图 1 演示了这种模式。

图 1. MVC 设计架构
MVC 设计架构
MVC 设计架构

Spring MVC 的强大之处

Spring MVC 实现了即用的 MVC 的核心概念。它为控制器和处理程序提供了大量与此模式相关的功能。并且当向 MVC 添加反转控制(Inversion of Control,IoC)时,它使应用程序高度解耦,提供了通过简单的配置更改即可动态更改组件的灵活性。Spring MVC 为您提供了完全控制应用程序的各个方面的力量。

Spring 的 Web MVC 模块是围绕 DispatcherServlet 而设计的。DispatcherServlet 给处理程序分派请求,执行视图解析,并且处理语言环境和主题解析,此外还为上传文件提供支持。

DispatcherServlet 通过使用处理程序映射来决定哪一个处理程序应当处理传入的请求。处理程序映射只是用于标识使用哪一个处理程序来处理特定 URL 模式的映射。处理程序是只有一种方法 ModelAndView handleRequest(request,response) 的控制器接口的实现。Spring 还有一些可用的高级处理程序实现;其中一个重要的高级处理程序实现是 SimpleFormController,它提供了将命令对象绑定到表单、对其执行验证等功能。

您已经在本系列教程的先前教程中使用了 DispatcherServlet 和简单的处理程序。在下一个部分中,将使用 SimpleFormController 并说明 Spring MVC 提供的各种即用功能。

用 Spring MVC 扩展电话本应用程序

在此部分中,将扩展电话本样例应用程序以使用 Spring MVC。

对电话本应用程序进行修改

在本教程中,您将对电话本应用程序进行下列修改:

  • 开发一个用于主页的控制器。此控制器将处理来自主页的所有操作。
  • 使用表单支持命令对象将电话本条目填充到主页中。您将看到支持命令对象如何高效地解决从主页本身获取电话本条目列表的问题。
  • 开发一个用于 Add Entry 页面的控制器。
  • 将 Add Entry 页面中的表单元素绑定到命令对象 (PhonebookEntry)。
  • 添加执行表单提交验证的代码。
  • 将成功验证的条目添加到数据库中。

在执行这些更改的过程中,将按照定义新控制器和新 JSP 页面的相同步骤添加修改和删除条目的功能。

工作区的目录结构

图 2 向您展示了应用程序的布局。从 下载 部分下载源压缩文件,并最好将其解压缩到根目录中。

图 2. 解压缩源文件后的应用程序目录结构
解压缩源文件后的应用程序目录结构
解压缩源文件后的应用程序目录结构

这里将会发生什么情况?

在开始开发本教程中使用的各个组件之前,请先来简要了解一下各个组件的角色。图 3 演示了 DispatcherServlet 如何与 Spring MVC 类结合使用。此图中显示的概念和流程是将要在本教程中创建的所有控制器和 JSP 页面的基础。

图 3. 显示 Spring MVC 发挥作用的事件流
显示活动中的 Spring MVC 的事件流
显示活动中的 Spring MVC 的事件流

在任何 Web 应用程序中,GET 请求通常都表示一个表单打开事件,POST 请求表示一个表单提交事件。Spring SimpleFormController 是使用相同的设计原理来设计的。下面是此控制器中的一些重要方法:

  • formBackingObject():当控制器收到一个表示表单打开的 GET 请求时调用此方法。这是可以创建并返回带有在表单被打开时需要显示的数据的命令对象的位置。
  • Validate():表单被提交后,控制器将先把表单元素绑定到命令对象上。绑定成功后,它将调用验证器上的验证方法并传递刚创建的命令对象。
  • onSubmit():如果命令对象验证成功,则调用此方法。这是可以对输入的数据采取操作的位置,例如将数据保存到持久稳固的库中。

此外,请记住这些方法是根据请求装入所有 JSP 页面的方法,并且是控制器处理操作的方法。掌握了这些信息,您就已经准备好开始开发构成此应用程序的组件了。

引入 PhonebookHomeController

SimpleFormController 类是 Spring MVC 的 FormController 的具体类。它在用相应的命令对象创建表单时提供支持。SimpleFormController 允许您指定命令对象、表单的视图名称、表单提交成功时需要显示给用户的页面的视图名称等等。它还允许在验证错误的情况下重新提交到表单视图。

通过覆盖 formBackingObject 方法可以定制这个 SimpleFormController 类。您将使用此控制器将电话本条目填充到主页中。如前述,formBackingObject 是可以用于实现此目的的方法。另请注意,您将使用 PhonebookEntry 对象作为命令对象。清单 1 显示了如何定义此控制器。

清单 1. 定义 PhonebookHomeController
public class PhonebookHomeController extends SimpleFormController{
    
    private IPhonebookDataProvider pbDataProvider; 

    protected ModelAndView onSubmit(Object command) throws Exception {
        return new ModelAndView(new RedirectView(getSuccessView()));
    }    
    
   
    public IPhonebookDataProvider getPbDataProvider() {
        return pbDataProvider;
    }

    public void setPbDataProvider(IPhonebookDataProvider pbDataProvider) {
        this.pbDataProvider = pbDataProvider;
    }

    protected Object formBackingObject(HttpServletRequest request) throws Exception {
        WebApplicationContext ctx = 
WebApplicationContextUtils.getWebApplicationContext(request.getSession().
getServletContext());
        IPhonebookDataProvider pb = (IPhonebookDataProvider) ctx.getBean("phonebook");
        List phonebookEntries = pb.getPhonebookEntries();
        return phonebookEntries;
    }   
}

清单 1 中可以看到 formBackingObject 具有提供电话本条目列表的逻辑。但由于 formBackingObject 需要获取此列表,因此它需要使用一个 IPhonebookDataProvider 类型的对象。您将使用 IoC 并将此属性注入控制器中,下一部分将对其加以说明。现在,了解一下如何配置控制器使其处理以下 /home-mvc.act URL 模式。

让应用程序上下文知道新控制器的存在

下一步是让在 phonebook-servlet.xml 中定义的应用程序上下文知道刚定义的新控制器的存在。此步骤包括定义两个 bean,如清单 2 所示。

清单 2. 在 phonebook-servlet.xml 中添加 PhonebookHomeController 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="/home-mvc.act">phonebookHomeController</prop>
                    <prop 
key="/addentry-mvc.act">addEntryFormController</prop>
                    <prop 
key="/modifyentry-mvc.act">modifyEntryFormController</prop>
                    <prop 
key="/deleteentry-mvc.act">deleteEntryFormController</prop>
            </props>
        </property>
    </bean>   


<bean id="phonebookHomeController" 
class="phonebook.controller.PhonebookHomeController">
        <property 
name="sessionForm"><value>true</value></property>
        <property 
name="bindOnNewForm"><value>true</value></property>
        <property 
name="formView"><value>home-mvc</value></property>
        <property 
name="successView"><value>addentry-mvc</value></property>
        <property 
name="commandName"><value>phonebookEntries</value>
</property>
        <property 
name="commandClass"><value>java.util.List</value></property>
         <property name="pbDataProvider">
            <ref bean="phonebook"/>
        </property>
    </bean>

您在本系列教程的先前部分中看到了如何将 URL 模式映射到特定的控制器。现在在这里的第一个 bean 定义中要做同样的操作。

第二个 bean 是控制器配置,使应用程序上下文知道新控制器的存在。下面细分了 Spring MVC 建议使用的 bean 定义的其他重要属性:

  • sessionForm:此属性用于表示此表单是不是会话表单。如果是,控制器将在第一次请求时调用 formBackingObject 方法并保留会话中返回的命令对象。如果需要处理验证错误同时仍保留用户可能已经输入的任何数据,则使用此属性十分便利。
  • bindOnNewForm:如果为真,控制器还将执行新表单与命令对象的绑定。
  • formView:表单视图是在对此控制器发出请求时应当打开的视图。它还是任何验证失败的默认视图。
  • successView:成功处理 onSubmit 方法后将打开此视图。
  • commandName:这是绑定后控制器所创建的命令对象的名称,或者是可在 JSP 页面中访问的 formBackingObject 方法所返回的命令对象的名称。
  • commandClass:此属性用于指定命令对象的类。
  • pbDataProvider:这是一个用户定义的属性,表示您定义的 bean。需要使用此对象来访问数据库中的数据。您可以将此属性设为在 第 4 部分 中创建的 AOP 代理的对象,以使用事务和跟踪。

定义和配置主页控制器的工作现在已经完成。下一步是启用 MVC 的 home-mvc.jsp 页面。

Spring MVC 对 JSP 的支持

此部分向您展示了 Spring Framework 在 JSP 方面提供的支持。您将了解 Spring 如何将 Java 对象作为命令对象传递给视图(在本例中为 JSP 页面)。请继续前进并更改主页以使其启用 MVC。

让视图 (home.jsp) 启用 MVC

对由先前在 第 4 部分 中实现的主页演变而来的 home-mvc.jsp 不需要做大量更改。但是,如果查看清单 3,则会注意到此页面中的所有 Java 代码几乎都已被删除。它还使用了 JSTL(特别是 c:forEach 标记)来实现重复操作。注意 c:forEach 标记的参数是 ${phonebookEntries}。此对象的名称与配置主页控制器时指定的命令名称相同。需要注意的另一个更改是调用 addentry-mvc.jsp 的 JavaScript 代码。在本例中指定的操作是 /phonebook/addentry-mvc.act,它将映射到在后面部分中定义的 AddEntryFormController。另请注意,表单提交方法为 GET。同先前说明的一样,GET 将表示一个表单打开。

清单 3. 做出更改来为主页启用 MVC
...
<html>

<script type="text/javascript">
function goToAddEntryPage()
{
   document.myForm.action="/phonebook/addentry-mvc.act";
   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>
...
    <form name="myForm" action="" method=post>

        <!-- 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>
                
            <c:forEach items="${phonebookEntries}" var="pbEntry">

            <TR>
                <TD align=center><input type=radio name="data_i" 
alt="Select to Modify or Delete" align="middle" 
onclick="javascript:setId(${pbEntry.entryID},${pbEntry.rowID})"></TD>
                <TD align=center><c:out 
value="${pbEntry.firstName}"/>&nbsp;<c:out 
value="${pbEntry.lastName}"/></TD>
                <TD align=center><c:out 
value="${pbEntry.homeNumber}"/></TD>
                <TD align=center><c:out 
value="${pbEntry.workNumber}"/></TD>
                <TD align=center><c:out 
value="${pbEntry.cellNumber}"/></TD>
                <TD align=center><c:out 
value="${pbEntry.email}"/></TD>
            </TR>
            
            </c:forEach>
            
            <input type="hidden" name="entryID" value="" />
            <input type="hidden" name="rowID" value="" />
            
        </TABLE>
      
        <table align=center>       
            <!-- The row containing command buttons -->
            <TR align=center>
                <TD>
                <input type=submit name="Add" value="Add an Entry" 
onclick="javascript:goToAddEntryPage()"></TD> 
                <TD><input type=button name="Modify" value="Modify Selected 
Entry" onclick="javascript:goToModifyEntryPage()"></TD>
                <TD><input type=button name="Delete" value="Delete Selected 
Entry" onclick="javascript:deleteEntry()"></TD>
            </TR>
        </table>
...

此时,对主页的操作就已完成。接下来,您将为 addEntry 页面启用 MVC。

引入 AddEntryFormController

需要 AddEntry 控制器执行的操作包括装入页面和将新条目保存到数据库中。这些操作将使用您在 homeController 中使用的同一个 SimpleFormController 类。首先来看看用于此类的清单 4。

清单 4. 定义与主页控制器类似的 AddEntryFormController
public class AddEntryFormController extends SimpleFormController{
    
    private IPhonebookDataProvider pbDataProvider; 
    
    protected ModelAndView onSubmit(Object command) throws Exception {

        System.out.println("ON SUBMIT CALLED......");
        PhonebookEntry phonebookEntry = (PhonebookEntry)command;
        pbDataProvider.addEntry(phonebookEntry);
        return new ModelAndView(new RedirectView(getSuccessView()+".act"));
        
    }    
    
    protected Object formBackingObject(HttpServletRequest request) throws Exception {
        PhonebookEntry phonebookEntry = new PhonebookEntry();
        return phonebookEntry;
    }

    
    public IPhonebookDataProvider getPbDataProvider() {
        return pbDataProvider;
    }

    public void setPbDataProvider(IPhonebookDataProvider pbDataProvider) {
        this.pbDataProvider = pbDataProvider;
    }
    
}

正如您所见,清单 4 中的代码与为主页控制器执行的操作没有什么不同。这里惟一的改变是在表单打开时返回一个空命令对象,因为在表单打开时无需填充任何数据。此外,这里添加了处理 onSubmit 事件的逻辑,因为在执行此操作时需要把条目添加到数据库中。在将用户输入保存到数据库中之前先验证用户输入不是很好么?请继续学习并引入 PhonebookEntryValidator 类来具体执行此操作。

引入 PhonebookEntryValidator

使用此类的惟一一个目的是验证用户在 Add Entry 页面中输入的数据。您立刻就会认识到添加数据验证代码并将验证错误作为对象返回是多么地干净和轻松。清单 5 显示了 PhonebookEntryValidator 类的代码。

清单 5. 用于处理数据验证的验证类
public class PhonebookEntryValidator implements Validator {
    public boolean supports(Class clazz) { return 
clazz.equals(PhonebookEntry.class); }
    
    public void validate(Object o, Errors errors) {
        validatePhonebookEntry((PhonebookEntry)o, errors);
    }
    
    public void validatePhonebookEntry(PhonebookEntry pbEntry, Errors errors) {
   
        if ("".equals(pbEntry.getFirstName()) || pbEntry.getFirstName() == null) {
            errors.rejectValue("firstName",null, "First name cannot be 
empty");
            }
        if ("".equals(pbEntry.getLastName()) || pbEntry.getLastName() == null) {
            errors.rejectValue("lastName",null, "Last name cannot not be empty");
            }
    }
}

根据 Spring MVC 规范,验证类需要实现 Validator 接口。清单 5 中定义的类就是这样做的。在这里,最重要的方法是 supports() 方法,它用于确保此验证程序仅用于您的命令对象 (PhonebookEntry)。

validate 方法是需要添加验证代码的位置。名字和姓氏不允许为空。validate 方法将收到一个已与表单绑定的命令对象和一个 Errors 对象。通过 Errors 对象可以拒绝用户已经输入的值。注意,rejectsValue 方法的第一个参数应当与命令对象的对应属性完全匹配。Spring 将使用反射 API 把错误与对应的 Command 对象属性直接关联起来。

下一步将向您展示如何将此验证程序动态地注入 addEntry 控制器中。

让应用程序上下文知道用于 addEntry 的新控制器的存在

下一步是更改 phonebook-servlet.xml 文件以映射这个新控制器的 URL。清单 6 中突出显示了该配置中的重要行。

清单 6. 在应用程序上下文文件中添加 addEntryFormController 的配置
<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>
            </props>
        </property>
    </bean>

<bean id="phonebookEntryValidator" 
class="phonebook.controller.PhonebookEntryValidator"/>

<bean id="addEntryFormController" 
class="phonebook.controller.AddEntryFormController">
        <property 
name="sessionForm"><value>true</value></property>
        <property 
name="bindOnNewForm"><value>false</value></property>
        <property 
name="commandName"><value>phonebookEntry</value></property>
        <property 
name="commandClass"><value>phonebook.dao.PhonebookEntry</value><
/property>
        <property name="validator"><ref 
bean="phonebookEntryValidator"/></property>
        <property 
name="formView"><value>addentry-mvc</value></property>
        <property 
name="successView"><value>addentry-mvc</value></property>
        <property name="pbDataProvider">
            <ref bean="phonebook"/>
        </property>
    </bean>

如果查看 ID 为 phonebookEntryValidator 的第二个 bean 定义,它定义了将与 addEntryFormController(在下一个 bean 定义中定义)结合使用的 validator bean。addEntryFormController bean 将定义用于应用程序的 Add Entry 页面的控制器。其中需要注意的最重要的属性是 validator。当 ApplicationContext 读取此属性时,它使控制器知道还必须使用 phonebookEntryValidator bean 来验证它所处理的视图。

现在可以为 addentry-mvc.jsp 页面启用 MVC。

Spring MVC 中的数据绑定

在此部分中,您将更改 addEntry.jsp 页面使其与 Spring MVC 协作。您还将看到如何使用 Spring MVC 标记库把数据对象与视图(JSP 页面中的元素)绑定起来。

为第二个视图页面 (addEntry.jsp) 启用 MVC

将使用 JSTL 标记库中标记把命令对象与输入字段绑定起来。Spring MVC API 附带了它自己的标记库描述符 (TLD) 文件。使用该文件可以实现命令对象的动态绑定。首先,看一看清单 7 中的这段 JSP 代码。

清单 7. addEntry.jsp 中的 MVC 绑定代码
<%@ include file="/WEB-INF/jsp/header.jsp" %>
...   
<html>

<script type="text/javascript">
 function doSave()
 {
    document.myForm.pageAction.value="ADD";
    document.myForm.action="/phonebook/addentry-mvc.act";
    document.myForm.submit();
 }
 
 function doReset()
 {
    document.myForm.reset();
 }
 
 function goHome()
 {
    document.myForm.action="/phonebook/home-mvc.act";
    document.myForm.method="GET";
     document.myForm.submit();
 }
 
</script>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Phone Book</title>
    </head>
    <body>
    <form name="myForm" action="" method=post>
    <fieldset>
        <!--The Heading of the Phone Book Page-->
        <h1 align=center>Phone Book - Add Entry</h1>
    
        <BR>
        <BR>
        <!-- The table containing phone book contents. -->
   
        <TABLE border="1" width="100%">
                
            <TH width="15%" align=center>First Name</TH>
            <TH width="15%" align=center>Last 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>
            
            < spring:bind path="phonebookEntry">
              <font color="red">
                <b><c:out value="${status.errorMessage}"/></b>
              </font>
            </spring:bind>

            
            <spring:nestedPath path="phonebookEntry">
            <!-- Add Spring bindings here -->
            <TR>
                <spring:bind path="firstName">
                    <TD align=center><input type=text 
name="${status.expression}" value="${status.value}" alt="Enter First Name" 
align="middle"/></TD>
                    <c:if test="${status.error}">
                              <span class="error"><c:out 
value="${status.errorMessage}"/></span>
                    </c:if>
                </spring:bind>
                
                <spring:bind path="lastName">
                    <TD align=center><input type=text name="<c:out 
value="${status.expression}"/>" value="<c:out value="${status.value}"/>" 
alt="Enter Last Name" align="middle"/></TD>
                    <c:if test="${status.error}">
            <span class="error"><c:out 
value="${status.errorMessage}"/></span>
                    </c:if>
                </spring:bind>
...
            </spring:nestedPath>
        </TABLE>
 
       
        <table align=center>       
            <!-- The row containing command buttons -->
            <TR align=center>
                <TD><input type=button name="Save" value="Save the Entry" 
onclick="javascript:doSave()"></TD> 
                <TD><input type=button name="Reset" value="Reset" 
onclick="javascript:doReset()"></TD>
                <TD><input type=button name="Home" value="Go to Home Page" 
onclick="javascript:goHome()"></TD>
            </TR>
...

注意 清单 7 中所示的表单中的 spring:bind 标记。spring:bind 标记把标记中包含的表单元素与 path 属性所标识的对应属性绑定起来(参见清单 8)。

清单 8. 来自清单 7 的 spring:bind 标记
<spring:nestedPath path="phonebookEntry">

<spring:bind path="firstName">
                   <TD align=center>>input type=text 
name="${status.expression}" value="${status.value}" alt="Enter First Name" 
align="middle"/></TD>
                    <c:if test="${status.error}">
                              <span class="error"><c:out 
value="${status.errorMessage}"/></span>
                    </c:if>
</spring:bind>

此绑定规范将把输出的表单元素与命令对象 phonebookEntryfirstName 属性绑定起来。注意,如果不在嵌套的路径内指定 Spring 绑定,则必须访问作为 phonebookEntry.firstNamefirstName 属性。

Status 是一个可用于描述绑定状态的 JSP 页面的特殊变量。下面是其各种属性的简要介绍:

  • Status.expression 包含正被绑定的属性的名称。
  • Status.value 包含可以用于填充输入字段的属性的值。
  • Status.error 标识绑定是否导致了错误。
  • 如果绑定导致了错误,Status.errorMessage 将用于包含绑定错误消息。

现在,主页和 Add Entry 页面完全启用了 MVC。接下来,您将看到如何创建具有添加、修改和删除功能的新页面。

定义新的修改和删除页面

了解了使用 Spring MVC 创建主页和 Add Entry 页面所需的简单步骤。您将使用相同的步骤来创建主页和 Add Entry 页面以创建修改页面和删除页面。

最后几步

几乎完成了!需要更改 Web 配置文件把应用程序集成到 Geronimo 中。此部分向您展示了如何使应用服务器知道启用了 MVC 的页面的存在。通过在 web.xml 文件中添加新的 URL 映射完成此操作。

启用了 Spring 的页面的 URL 映射

您只有最后一件事要做:在 web.xml 中添加 *.act 的映射。要使代码保持干净,需要定义一个新的 URL 映射。采用这种方法,如果您是一直按照本系列教程操作的,则仍能够访问在先前教程中创建的其他页面。

在 web.xml 中添加 *.act 作为新映射

这是一个简单的映射,用于让应用服务器知道传入 *.act 的任何请求都应当被转发给默认的 Spring DispatcherServletDispatcherServlet 将通过读取在先前部分中定义的配置知道哪一个控制器已被配置。清单 9 显示了 web.xml 文件中增加的这段代码。

清单 9. 将 *.act 映射到 DispatcherServlet
  <servlet>
    <servlet-name>phonebook</servlet-name>
    
<servlet-class>org.springframework.web.servlet.DispatcherServlet
</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
 
   <servlet-mapping>
    <servlet-name>phonebook</servlet-name>
    <url-pattern>*.act</url-pattern>
  </servlet-mapping>

此映射将把匹配 *.act 的 URL 定向到电话本分派程序 servlet。

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

Spring MVC 发挥作用

好的!随着 Web 应用程序的开发完成了 MVC 的启用。现在需要构建和运行它。此部分向您展示了查看 Spring MVC 发挥作用需要采取的步骤。

构建、部署和运行应用程序

本教程附带的源压缩文件包括所有必要的类、配置文件和 Ant 构建文件(如果要构建它)(有关链接,请参阅 下载)。压缩文件中还有一个包括所有必需内容的可部署 .war 文件。

必须确保 readme.txt 文件中提及的所有 .jar 文件都位于 <WORKSPACE</phonebook/lib 目录中。请参阅本系列教程的 第 2 部分 中的构建和打包说明(如果需要)。

现在,使用 Geronimo 中的 Deploy New 工具来部署 phonebook.war 文件。如果一切都按照计划运行,您将在 Geronimo Web 控制台中看到以下消息:Phonebook application deployed successfully

现在只需将浏览器指向新页面:http://localhost:8080/phonebook/home-mvc.act。如果一切运行正常,主页应当显示如图 4 所示的内容。

图 4. 应用服务器中运行的 home-mvc.act
应用服务器中运行的 home-mvc.act
应用服务器中运行的 home-mvc.act

您应当会在 Geronimo 控制台中看到系统输出消息,显示在本系列教程的 第 4 部分 中定义的所有建议都被执行。

添加错误处理

您可以将一般错误处理轻松地添加到此应用程序中。必需的全部操作包括在 web.xml 中指定错误页面条目并定义一个错误页面。清单 10 显示了 web.xml 中的条目。

清单 10. 配置错误页面来处理异常
  <error-page>
    <exception-type>java.lang.Throwable</exception-type>
    <location>/WEB-INF/jsp/errorpage.jsp</location>
  </error-page>
  
  <error-page>
    <exception-type>500</exception-type>
    <location>/WEB-INF/jsp/errorpage.jsp</location>
  </error-page>

在抛出任何异常或出现 500 错误代码的情况下,清单 10 中的配置将把流程定向到 errorpage.jsp。Errorpage.jsp 仅以一种友好的方式显示错误消息(参见清单 11)。

清单 11. ErrorPage 的 JSP 代码
...
<TR>
  <TD>Your request to the following URI: ${pageContext.errorData.requestURI} 
has failed. </TD>
</TR>  

<TR>
  <TD>Status code: ${pageContext.errorData.statusCode}</TD>
</TR>

<TR>
  <TD>Exception: ${pageContext.errorData.throwable}</TD>
</TR>
...

您将在本教程所提供的源代码中看到 Errorpage.jsp 以及其他 JSP(请参阅 下载)。还对 web.xml 做了更改。您只需编译并重新部署应用程序即可看到它运行。

使用 Spring 框架的优点

您的操作已经涉及到了 Spring 框架中最常用的一个模块:Spring MVC。它提供了一整套用于构建 Web 应用程序的 MVC 功能。Spring 的可移植架构允许您使用内置 MVC 框架或其他框架(例如 Struts)。

使用 Spring 的 Web 模块的优点包括:

  • 清晰的角色划分:Spring MVC 有一组经过良好定义的类,用于控制器、验证程序、命令对象、表单对象、处理程序、视图解析程序等等。这些类中的每个类都是一个履行单独任务的专门对象。
  • 能够将框架类和应用程序类配置为 JavaBean。
  • 可重用的业务代码:Spring 的架构允许您使用现有的业务对象作为命令对象或表单对象。使用诸如 Struts 之类的其他框架都不可能轻松地实现这一点。
  • 自定义绑定和验证:Spring MVC 定义的对象的绑定规则都是可以高度自定义的。您可以直接将视图绑定到 Java 对象上,而不是绑定到字符串上。
  • 灵活的视图解析及处理程序映射:使用 Spring MVC,您可以使用大量视图解析策略。通常使用它附带的视图解析策略,但是您能够定义自己的策略(基于您的要求)并将其插入。
  • 简单的本地解析和主题解析。
  • 并且最后,一个简单但是功能强大的标记库将尝试避免花费任何代价来生成 HTML,使在构造代码方面具有最大的灵活性。

结束语

本教程向您展示了 Spring MVC 模块是基于一种干净清晰的设计的。分发时它预打包了很多可以在应用程序中直接使用的类。Spring 的架构的妙处在于它可以轻松地引入您认为应用程序所必需的那些功能。

本教程中引入的控制器十分易于理解。最困难的部分是将命令对象绑定到视图上。Geronimo Web 控制台再一次使执行部署应用程序的任务变得更加轻松。

在本系列教程的最后一部分第 6 部分中,您将继续了解与 MVC 相关的技术。您将更详细地了解 JSP,还将更深入地研究 Spring MVC,方法为论证 MVC 相对于特定实现的独立性(例如,JSP 与其他 UI 技术对比),以及这种独立性为 Geronimo 应用程序提供的优势。将用 Spring MVC 中构建的各种 Web 视图做试验,演示 Velocity 和 Tile 等模板技术。第 6 部分附带了一个示例,展示怎样才能使用 Spring MVC 的内置 PDF 支持类导出 PDF。敬请关注!


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology, WebSphere
ArticleID=199322
ArticleTitle=Apache Geronimo 和 Spring 框架,第 5 部分: Spring MVC
publish-date=03152007