學習筆記,每一天我們都在進步。
2006/12/07 00:20
 
A human being should be able to change a diaper, plan an invasion, butcher a hog, conn a ship, design a building, write a sonnet, balance accounts, build a wall, set a bone, comfort the dying, take orders, give orders, cooperate, act alone, solve equations, analyze a new problem, pitch manure, program a computer, cook a tasty meal, fight efficiently, die gallantly. Specialization is for insects.

—Excerpt from the notebooks of Lazarus Long, from Robert Heinlein’s Time Enough for Love

3.1. 被支柱支撑的支柱

现在,各种团队都在编写各种各样的web 应用。在应用中使用分层架构 [POSA] ,如第1 章和第2 章所述,可以很容易地让团队的成员分别专注于应用的不同部分。但是,仍然很有必要让团队的每个人都从头到尾理解整个处理流程。在我们深入探究Struts 的各部分是如何优雅的相互结合之前,让我们先从头开始构建一个简单但有用的程序。在这一章,我们将通过教程、逐步解剖来和Struts 来一个亲密接触,然后构建一个供用户登入和登出的程序。

虽然没有第一章的例子显得细微,我们仍然暂时保持例子的简单性。在第4 章才会涉及到一个实际的应用。

在本章,我们将从用户的角度来讨论一个经典的登录logon 应用。第一章的练习只是对从注册页面输入的密码信息进行比较,根据是否匹配来控制页面流的分支跳转。该应用让您使用第1 章的练习所创建的账户来进行实际登录。控制流和页面内容的改变取决于你的状态。

引入这个应用后,我们将对它进行分解来仔细探讨其各个部分。如果你的机器上安装了Struts 开发环境,你可以跟着来做。当然,你也可以舒服地靠在你的椅子上,喝着卡普契洛咖啡,来看它是如何工作的。

然后,打好基础后,我们就开始一步步构建这个应用。

每部分的表述都力图使你可以根据它自行完成,目的是你可以编写之(而不是单调的重写工作)。如果你在你的计算机前面,你可以跟着我们来输入源代码。如果不,每部分中的足够的细节也使你可以只看书就跟上我们。

3.1.1. 为什么选择 logon应用?

我们的例子程序允许用户登录到应用中去。一旦用户登入,页面的改变将反映出用户已经得到授权。一般情况下,这都是大型应用的第一步,授权用户进入,进行它们感兴趣的操作。但我们现在的目的,只是登录进一个用户,这已经足够用来演示Struts 应用是如何实际工作的了。

如表3.1 所示,我们选择 logon 应用仅仅因为它易于理解,简单,自包含,而且在许多应用中都需要。

表格 3-1 为什么选择登录应用

原因

解释

好理解

我们几乎都需要登录到应用的共享部分,所以这个流程很

 好理解
简单,且自包含 一个接受用户登录的应用可以写得很简单并且是自包含
的,不需要复杂的模型  
被许多应用需要 我们几乎都需要编写使用某种登录流程的应用,所以这就
是我们需要的代码

3.2. 漫游logon应用

在开始我们的Struts 的漫游之旅之前,我们先讨论这个应用的范围,以及你如何才能跟得上我们的节奏。然后我们来看看应用中使用的屏幕,请注意在登录以后是如何变化的。我们完成这个漫游之后,会再回头过来看一看。

3.2.1. 从这里开始

我们创建logon 应用的目的是给你一个关于Struts 的各个部件间是如何配合工作的印象。为了不至于跑题,这个应用仅包含我们需要用来展示整个框架的那些组件。它没有包含实际的业务逻辑,单元测试,或者漂亮的对话框。虽然这些东西对一个要交付的成型产品来说是很重要的,但在会跑之前我们得先学会走。

Logon 应用也有意搞得很刻板。它没有修饰HTML 来愉悦眼睛—仅仅是一些我们需要来接受登录的初步功能。

当然, 你的Struts 应用可以编写得很漂亮,只要你高兴。

如果你愿意在你的机器上运行这个应用,请访问本书网站的下载页面 [Husted]。这是一个可以自动部署的WAR 包。

我们并不要求你保持打开这个应用,但在某些时候是很有趣的。你需要跟着做的东西都在本章列出来了。

首先,我们从用户的角度来漫游一下各个屏幕。然后,再回过头来看看如何编写实际的代码。

3.2.2. 我们看到的屏幕

如表5 所示,logon 应用有两个屏幕: 欢迎屏幕和登录屏幕。

如果你跟着做,并将应用部署在你的本地计算机上,你可以在浏览器中用以下地址访问欢迎页面:


表格 3-2 Logon 应用的屏幕

 
3.2.3. 欢迎屏幕

第一次访问欢迎屏幕时,它只有一个链接,“Sign in” (见图3-1)。如果点击这个链接,就会出现登录屏幕。


图 3-1 欢迎屏幕

3.2.4. 登录屏幕

登录屏幕输入和提交用户名和密码,如图3-2 所示。


图 3-2 登录屏幕

为了看看实际动作的登录表单,什么都不输入,点击提交。登录屏幕将返回,但伴随了错误提示消息,如图3-3。

 
图 3-3 登陆屏幕告诉缺少用户名和密码

如果你输入用户名但是忘记输入密码提交后,消息变为图3-4 所示:


图 3-4 缺少密码

这里有关工作流过程中重要的是,从用户的角度来看:„ 它马上告诉用户缺失所有信息

„  如果用户只是提交一个信息,它会提醒用户还缺另外一个信息;
„  它重新显示用户进入的屏幕,而不是要求用户按下Back键
„  用户设法输入用户名和密码,表单被接受,并显示下一个屏幕。

logon 应用根据一个属性文件来校验用户的输入,就象第1 章所用的那个文件。如果你从本书的网站上下载了这个logon 应用,你可以用本书的作者名字登陆,如表所示:

表格 3-3 缺省的登录用户

用户名  密码  
Ted  Husted  
Cedric  Dumoulin  
George  Franciscus  
David  Winterfeldt  

3.2.5. 重新显示欢迎屏幕

成功登录后,将重新显示欢迎屏幕---不过有两点不同。


图 3-5 用户登录后的欢迎屏幕

首先,它根据登录的用户做了调整。不再显示 “Welcome World!” 而是显示欢迎已经登录的具体的用户名字。

另外,你会注意到,加入了另一个链接,除了sign in 外你会还看到Sign out。

3.2.6. 欢迎屏幕,再见

为结束这个循环,点击 sign-out 链接, 我们将返回最初的欢迎屏幕,如图3-1 所示。

3.2.7. 所使用的特征

虽然简单,我们的这个应用却展示了以下的技术:


† 编写链接
† 编写表单
† 校验输入
† 显示错误消息
† 重装表单
† 显示代替文本虽然不明显,也展示了:
† 从动态页面引用图象
† 重写超链接在下一节,我们将深入应用的源代码去看看其核心特征是如何实现的。
3.3. 解剖 logon 应用

既然我们已经和Struts logon 应用打过招呼了,我们现在回过头去仔细打量一下它。

现在,我们将展示每个页面的代码以及它们的相关组件,并解释每一部分是干什么的。待介绍完所有的部分后,再来演示如何将它们组装在一起。

3.3.1. 欢迎屏幕的浏览器代码

你可以回头看看,我们的应用开始于一个欢迎屏幕。让我们来看一下这个页面在浏览器中的代码:黑体部分是显示在屏幕上的内容:

清单 3.1 欢迎页面的浏览器代码

如果你是个web 应用的新手,一个重要的事情是,请注意这里除了标准的HTML 外绝对不能有其它东西。事实上, 除了浏览器能理解的通用标记外,页面中也绝不可能有其它任何东西。所有的 web 应用都基于HTML 的限制,并不能做一些HTML 不能做的事情。Struts 可以很容易的使用 Velocity 模板, JSP, 以及其它表现系统来编写你想要的HTML,但是你所必须做的所有事情都必须使用浏览器能理解的标记。

 
3.3.2. 欢迎页面的JSP源代码

现在让我们看一下产生图3-1 页面的JSP 源代码。JSP 标记以黑体形式显示。

 
清单 3.2 欢迎页面的JSP 代码

现在我们来看看,黑体部分做些什么


这是JSP 中相当于import 语句的东西,它使标明的标签扩展在接下来的页面中有效。而代码


则产生一个标准的HTML base 标记, 以便对如图片这样的引用要相对于这个JSP 页面的地址。你可能会注意到有时logon 应用引用到.do 页面。这不是实际的服务器上的页面文件, 而是对开发人员编写的Java 类,或者Action 的引用。这些 Action 然后转发到产生响应的JSP 页面。

JSP 通常包括到HTML 资源如图片和样式表的引用。最方便的方法是通过相对于JSP 模版的路径来引用。但当Action 转发控制时,它并不事先通知浏览器。如果浏览器被给定一些相对路径,它就会根据Action URI 来解析它们, 而不是JSP 文件摸版的位置。

依赖于你何时访问欢迎页面, 其地址可能被浏览器显示为:


这对动态应用来说是个很常见的问题。HTML 规范[W3C, HTML] 提供了一个标记作为这个问题的解决方案。Struts 也提供了一个类似的html-base 标记,可以插入JSP 之中。如果你查看logon 页面的HTML 源代码,查询其外观地址,你就可以看到该标记被被渲染成:


这可以让浏览器找到“Powered by Struts” 图片,该图片也存放在pages 文件夹下面。现在,让我们来看这段代码:


你可能记得welcome 页面根据用户是否登录进去来定制页面显示。这一段就是检查是否在客户会话中存储了一个“user” bean 。如果这个bean 存在,则显示欢迎这个用户。

下面的代码显示了为什么维护用户会话是这么重要。(第3.3.1 节)。庆幸的是,Struts 标签和servlet 容器一起来自动维护会话(不管浏览器是否设置为使用cookie)。对开发人员来说,感到就象会话是内建在HTTP 里面一样—这就是框架的作用。框架扩展了基础环境,所以开发人员可以专注于更高级的任务。相反,如果user bean 不存在,我们使用一个通用的欢迎信息。所有的Struts 逻辑标签都使用 “this” 和 “notThis” 格式。其它标签则并未提供。

 
虽然这意味着要进行一些重复测试,但它简化了总体语法和标签的实现。当然,你也可以使用其它标签扩展。

你并未被局限于Struts 软件包中所提供的东西。其它志愿者贡献的标签扩展列于Struts 资源面 [ASF, Struts] ,甚至有提供if/then/else 语法的,你可以用它们作为替代。

如3.3.1 所述, Struts 自动重写超链接来维护用户会话。它也使你可以给链接取个逻辑名字并将实际的链接存放在配置文件之中。这就象通过关键字引用数据库一样,记录中的名字和地址可以根据需要进行更改,其它表格可以通过关键字来查找更新的版本。

这里:


使用 logon 作为关键字来查询存放到用户登录页面的记录。如果我们想改变这个链接,只需要在配置文件里面改写就行了。页面再其下次被渲染的时候将以新的链接开始。

这段代码结合了 和 标记,仅当用户已经登录进去时显示登出链接。


3.3.3. Welcome屏幕的配置源代码

Struts 用配置文件来定义应用的一些东西,包括链接的逻辑名称。这是一个XML 文档,Struts 在启动时读入,用来创建一个所需的对象数据库。各种Struts 组件都引用这个数据库来提供框架的各种服务。配置文件的缺省名称是struts-config.xml.。

因为配置文件要被各个不同的组件使用,全部展示这个文件可能会让我们昏头。现在我们仅提供与我们这个阶段相关的部分。后面,当我们全部构建应用时,我们会展示整个配置文件。

在最初的 welcome 屏幕中,我们引用了一个logon forward。它在配置文件中是这样定义的:


这儿,logon 是个关键字,用来查找链接的实际路径。在这里引用了Struts action,但 path 也可以引用到JSP 页面, Velocity 模板,HTML 页面, 或其他具有URI 的资源 [W3C, URI]。

 
3.3.4. logon 屏幕的浏览器代码

如果我们进入 welcome 页面的Signin 链接, 它将我们带到logon 屏幕( 图3-2)。下面是logon 屏幕的浏览器代码。同样,黑体部分是显示在屏幕上的内容:


清单 3.3 登录屏幕的浏览器代码


而下面是相应的JSP 代码:


清单 3.4 登录屏幕的JSP 代码让我们象前面一样一步步来看这个页面的各个部分。首先,象前面一样,使 Struts html 标签扩展在后面的页面中有效:


象Struts action 一样, 标记库 URI 是一个逻辑引用。标签库描述符(TLD)的位置在web.xml 文件中给出。

你还记得,我们如果没有输入信息就提交的话,将会显示一个错误信息。下面的标签就是渲染此错误消息的。如果没有错误消息,标记则什么都不输出,就象从页面消失了一样:


标签产生一个HTML 表单供数据输入。它也产生一个简单的JavaScript 来将光标移到第一个输入域上。

Action 属性是对配置文件中的ActionMapping 的引用。它告诉表单哪个JavaBean helper 类将用来组装 HTML 控件。Java-Bean helper 基于Struts 框架类ActionForm:

 
标签创建一个HTML输入控件供文本域输入。它也用JavaBean helper 的username 属性来组装这个输入域:


所以,如果这个表单被返回来做校验,并且最后提交的username 是Ted, 这个标记就会输出:


否则, 标签将使用JavaBean helper 类中标明的username 属性的缺省值初始化。通常, 那是个null, 当然也可以是任何值。

同样 标签也创建一个 HTML 输入控件:


Password 控件象一个文本域,但是显示文本为*而不是输入的字符。如果表单被返回校验,缺省下,password 标记会重写先前的值,而不用重新输入。如果你想每次重新输入password , 可以将redisplay 属性关闭。

如果初次登录失败, 这段代码将清除浏览器的缓存,并以重新输入password,而不管是否通过验证。


下面的标签创建标准的HTML Submit 和 Reset 按钮:


当表单提交时,将涉及两个框架对象:ActionForm 和 Action。这两个对象都必须由开发人员创建并包含应用细节。如图3-6,ActionServlet 使用 Struts 配置来决定使用哪个ActionForm 和Action 。

 
图 3-6 配置决定使用哪个Actionform 和Action

下面我们看看Struts 配置中的 logon 屏幕的 ActionForm 和Action 配置。然后再看看这些类的源代码。

3.3.5. logon 的配置源代码

logon 屏幕自身在配置文件中仅仅引用一个元素:/LogonSubmit ActionMapping。这个元素再依次引用其他两个对象, app.LogonForm 和app.LogonAction。全部3 个对象如下表所以:

元素 描述
/LogonSubmit  ActionMapping,封装了构建并提交一个HTML 表单给Struts 框
架所需要的各种细节
app.LogonForm  描述了HTML form 所需的属性
app.LogonAction  处理提交的表单

3.3.6. LogonSubmit配置
上一节, 我们说过 标签紧密配合Struts配置,使HTML表单更加有用:


action 参数告诉 标签究竟使用哪一个ActionMapping。这里, Struts 配置中的 mapping 可能会像这样:


表3-4 提供了提供一个关于mapping 中每个属性的意义的索引。


就象在第2 章所述,Struts 框架使用的许多对象和属性名字都是很模糊的。比如,name 属性并不是 mapping 的名字;而是 JavaBean helper 的名字, 或者是和这个mapping 一起使用的ActionForm bean 的名字。

相应的form bean 在配置中是这样设置的:


这个元素使logonForm 的逻辑名字和特定的 Java 类app.Logonform 相关。这是一个Struts ActionForm 类的子类。ActionForm 类提供标准的方法给框架使用, 包括validate 校验方法。

让我们先看看LogonForm 的源代码再回过头看LogonAction 。

表格 3-4 Actionmapping 设置属性


3.3.7. LogonForm 源代码

虽然HTML 给用户一个地方来输入数据,但却没有给应用一个地方来放置数据。

当用户点击提交时,浏览器收集表单中的数据,并按一个名-值对列表的方式发送给服务器。所以,如果用户在logon 页面输入username 和passwaord 并点击提交,应用所看见的是:

username=Ted password=LetMeIn

浏览器将所有的东西都按字符串提交。你可以使用JavaScript 校验来强迫用户在某个域里面只能输入数字,或者使用固定的数据格式,但是这也仅是镜花水月。所有的东西仍然以字符串的方式提交给服务器—而不象准备传递给Java方法的二进制对象。

重要的是要记住,这是浏览器和HTML 工作的方式。

Web 应用无法控制这些。Struts 之类的框架的存在是使我们必须做的事情做的最好。Struts 对HTTP 数据输入难题的解决方法是使用ActionForm 。在象Swing 之类的环境中,数据输入控件有一个内建的文本缓冲区,可以校验所输入的字符。当用户离开控件,缓冲区可以转换为二进制类型,可以传递给业务层。


不幸的是,HTTP/HTML 平台不提供可以缓冲、校验和输入转换的组件。所以Struts 框架提供了一个ActionForm (org.apache.struts.action.ActionForm)类来沟通web 浏览器和业务对象。ActionForm 提供了想要的缓冲/校验/转换机制,我们可以用来保证用户输入它们想要输入的东西。

当 HTML 表单提交时,名-值对被Struts 控制器获取,并应用到ActionForm 。ActionForm 是一个 JavaBean,有属性和HTML 表单控件中的域相对应。 Struts 比较ActionForm 属性的名称和输入名-值对的名称。当匹配时,控制器设置属性值为相关的输入域的值。其它的属性会被忽略。错过的属性会保持它们的缺省值(通常是null 或者false)。

这里是LogonForm 的公共属性:


大部分 Struts ActionForm 的属性看起来就象如此。节约的开发人员可以当作一个宏来创建它们的属性并简单地替换属性名称。其他人则可以使用这个代码骨架,并使用查找替换的方法进行改写。Struts 代码生成器可以通过解析HTML 和 JSP 来自动生成ActionForm 。基本ActionForm 也包括两个标准方法—reset 和validate 。

 
当使用ActionForm 作为向导工作流的一部分时,reset 方法非常有用。如果mapping 设置为请求范围,这个方法就没必要实现。

当 mapping 设置为validate=true, validate 方法就会在 formbean 从HTTP 请求中组装完成后被调用。validate 方法经常用于主要的外观校验。它仅仅检查数据 “看起来”正确,并且所有要求的域都提交上来。再说一下,这些都是Swing 控件在数据传输到应用之前内部做的事情。

你也可以手工作这些检查,或者使用象ValidatorForm 的机制(第12章),这种Form 可以从配置文件中创建。

下面是LogonForm 的validate 方法。它检查是否两个域都输入了一些数据。应用有一些关于用户名和密码的长度之类的要求,都可以在这里进行检验。


validate 返回的ActionErrors 对象是另一个框架类。如果validate 没有返回 null, 控制器对象将在请求上下文中的一个关键字下存储ActionErrors 对象。

标记知道关键字,将会在它们存在时渲染出错误消息。或者如果错误信息不存在,就什么都不做。

记号error.username.required 和error.password.required 也都是关键字。它们用于从Struts 消息资源文件中查找要显示的实际消息。每个场所可以有其自己的资源文件, 这使得消息容易被本地化。

Struts 消息资源文件也是用名-值对格式。我们所用的消息条目是:使没有使用本地化, Struts 应用资源文件也将集所有的消息收集到一个单独的地方,在此你可以对它们进行修改和修订,而不用触及任何Java 源代码。

 
3.3.8. LogonAction 源代码

收集数据项到ActionForm 并且执行了一些初始化校验后,控制器将FormBean 传递给Mapping 标明的Action 类。

Struts 架构期望你能用自己的Java 类来完成大部分的请求处理。JSP 页面可以渲染结果,但Action 则是获得结果的方式。

就象第2 章中看到,这就是熟知的MVC 或 Model 2 方式, Action 充当了一个请求分派器 dispatcher 。

当对一个Action 的请求被送到Struts servlet, 它通过调用perform (或execute)方法来调用(分派) Action。


下面是logonAction 的全部源代码:

   
清单 3.5 LogonAction 类的Java 代码(/WEB-INF/src/java/app/LogonAction.java)

Action 位于Struts 食物链的最顶层,所以会导入好些类。我们将每个类在此说明,以便你可以知道他们来自于何处。


如果我们比较懒,这个语句块可以写成:


但是这恐怕是没什么好处的。像许多Apache 产品一样,Struts 源代码遵循最好的实践经验并且是很全面的。我们在源代码中也要遵循这一套。虽然我们在这儿忽略了JavaDoc,但我


们的源代码和 Struts 源代码都是文档齐全的。

接下来,我们使用了一个 helper 方法来调用业务层方法。我们也可以将同一段代码放在Action 的perform 方法内,但是,我们强烈建议将通用业务方法和Struts 控制器代码分开。

如果你加入了一行(业务代码),很快就会是三行,五行,…然后你的Action 就象一个大球陷入泥浆 [Foote]。避免 “代码蔓延” 的方式就是在从Action 调用之前,将业务层代码封装到helper 方法中:


如我们在别处所说, Struts 1.1 使用新的execute 方法来替代原来的perform 方法,但是它们两个都可以工作。在这里我们使用perform 方法,以使代码可以运行在两个版本之中:


Action 的目的是将输出从 web 层带到业务层,即应用的其它部分生长之地。在这里, 我们从ActionForm (JavaBean helper)中获取username 和 password 并经它们存为平面的Strings:


然后我们就可以将username 和password Strings 传递给业务层功能,看是否通过校验。这儿,我们小心地将调用封装进一个单独的方法,并没有将代码直接放入Action 的perform 方法之中。这个调用是对类中另一个方法的调用,但也可以容易的对其它Java 类的方法进行调用:

 
isUserLogon 方法的API 说明,如果校验符合,它将返回 true,如果不符合则返回false, 以及如果它不知道结果 (比如,连接不到目录)则抛出一个异常。如果异常发生,Action 就会捕获它,将这个事件转换成ActionError, 并将,控制转发回input 页面。

如果业务层返回说logon 没有成功, Action 将送出错误消息,并将控制路由到输入页面。当ActionForm 的validate 方法失败时,也会做同样的事情。(第3.3.7 节):


因为错误并不适合于一个具体的属性, 我们在ActionErrors.GLOBAL_ERROR 关键字下记录此错误,而不是一个属性名。为了指出logon 本身无效,我们也在validate 外定义了一个错误信息。在Struts 应用资源中,它是:


显示时,表现层用适当的信息代替error.login.invalid 标记。如果有一个单独的信息针对用户的当前场所,则用户会收到一个消息的本地化版本。

如果业务层说登录成功,我们就会告诉浏览器保留用户的认证信用。Java Servlet 框架为此目的提供了一个用户会话。这儿我们存储用户的logonForm 到会话上下文之中。

每个用户都有一个上下文, 由servlet 容器负责维护。那些在上下文中存储有logonForm 的用户都是登录用户。否则,便是未登录用户:


这种策略称之为基于应用的安全。许多Java web 应用都使用这个方法,因为它轻便且易于实现。其它身份认证方法也可以用在我们的应用中。

Struts 框架依靠容器的默认记录系统。这儿,我们仅在当Web.xml 文件中Servlet 的debug level 设置为足够高时记录这个事件。

 
在web.xml 中设置为:


在生产应用中, 你可以将 debug 设置为 0, 这些项目将不会出现。如果要插入其它记录系统,开发者可以继承 Struts ActionServlet 类,并重写log 方法。


当所有的信息发出,所有的工作干完,perform 方法返回一个ActionForward 给控制器(ActionServlet) 。这里, 我们发送控制到success forward:


这个转发是在Struts 配置中定义:


既然我们已经登录,表现页面就会应该有点变化。所以将显示一个登出连接。

3.3.9. LogoffAction 源代码

请看图3-5 ,当用户登录进去后,欢迎页面是如何改变的。在Welcome.jsp 中, 标签首先查看是否在会话上下文中被 LogonAction 放入一个user bean:


然后置入一个 标记,引用forward:

 
在 Struts 配置中, logoff 是这样定义的:


这里的路径引用到.do 动作。这应该是个相应的/logoff ActionMapping ,也在Struts 配置中定义:


如你所见,/logoff 是个极其简单的mapping; 它仅仅是将控制传递给 LogoffAction 类, 没有其他特殊参数和设置。LogoffAction 类的工作也非常简单。只是将用户的logonForm 对象从会话上下文移除。如果没有logonForm 在会话上下文中, 用户就被认为是已经登出了。

我们来看看LogoffAction 的源代码:

 
清单 3.6 LogoffAction 的源代码

首先,我们获得logon 对象。本应用的做法是存放用户的logon 对象到会话的上下文, 并以Constants.USER_KEY 作为关键字, 所以我们会看到:


之前,如果debug level 设为为高,我们记录了一些细节。这是该类的核心行为。我们移除了存放在USER_KEY下面的对象,就意味着,用户登出了。


如果想要移除为用户存储在会话中的所有东西,可以简单的使用invalidate 方法:


但这也消灭了所有用户对象,比如用户场所locale, 它要用来进行本地化。当登录操作完成后,返回到 welcome 页面:

// Return success

return (mapping.findForward(Constants.SUCCESS));


在下一节,我们要看看应用是如何从头构造的。到此,我们已经讨论了页面和类的内部细节。现在我们将眼光放在Struts 配置文件和源代码结构树。

3.4. 构造应用

我们已经详细探讨了应用的驱动,踢了踢轮胎,并检查了引擎盖下面的东西,准备上路了。我们已经知道了要做什么,并且怎样做,但是从那里开始构建的你应用呢?

在这一节,我们回到开头并向你展示可以如何从头至尾建立你的应用。因为我们已经有一个很好的关于我们想要应用干什么的印象,我们从一个实际的需求集开始。这样我们可以创建一个白板计划,包括那些明显的对象以及我们准备叫它们什么名称。然后我们就可以开始为对象编码,精化和扩展所作的计划。这种计划/编码,再精化-计划/再精化-编码的方法称之为 “螺旋式” 方法。

当然,有其它方式来进行软件开发。其它方法也可以很好的用于Struts 。但本节的目的并不是节是软件开发方法论。我们想要的是展示,构架一个简单的Struts 应用需要做些什么。所以,让我们开始上路喽...

3.4.1. 定义需求

Requirements are the effects that the computer is to exert in the problem domain, by virtue of the computer’s programming —Practical Software Requirements, by Benjamin L. Kovitz

既然我们已经有一个关于应用需要做些什么的充分理解,从一个需求集开始是一个很好的实践方法。下面的章节,我们将汲取最简单有用的需求集。

我们的简单需求文档包括3 个主要部分:目标、需求、规约。

表格 3-5 需求文档的主要标题

标题  目的  
目标 我们在问题领域中需要达到的结果  
域需求 为达到目标,我们需要实现的东西  
编程规约 我们要实现需求需要做的事情

目标l

† 允许有权限的用户向应用标识自己

领域需求

† 允许用户递交他们的身份信用信息(username 和password)
† 检验递交的信息是否有效
† 允许纠正无效的信用信息
† 身分信用信息有效时,通知用户
† 允许校验有效的用户访问需要权限的特征
† 允许用户在需要时,使访问无效

程序规约

† 可以从标准的web 浏览器访问, 可以使用或不使用JavaScript
† 为新访问者从welcome 页面提供登陆链接
† 允许在logon 页面输入用户信用(身份)信息
† 要求每个身份信息包含1-20个字符
† 要求输入username 和password
† 提交信息给业务层方法来进行校验
† 返回无效的身份信息给用户做纠正
† 如果身份有效,使用户登入
† 登陆后,用用户名定制welcome 页面
† 允许登陆的用户从页面登出
当然,这是一个非常简单的规约。许多规约要消耗大量的纸张,并用很多图, 数据表, 屏幕定义和问题域的详细描述来进行修饰。

3.4.2. 规划应用

根据手头的这些需求,我们可以开始勾画应用并规划要用哪些对象来实现这些程序规范。一个方法是列出规约的列表,以及有助于实现他们的组件清单。通常,团队做这种事情是在一个大白板上进行,作为一个初始项目会议的一部分。


视图

实际上,大多数应用都是以一个故事板(情节串联板)开始的。JSP 定义应用的可见部分。下面是表现层的大概要求。

表格 3-6 白板视图规划

规约 JSP页面
向新访问者提供从欢迎页面开始的登陆 Welcome.jsp  
允许在登陆页面输入用户身份信息(username 和 Welcome.jsp  
password)  
返回无效的身份信息给用户纠正 Logon.jsp  
当用户登陆进去时,根据用户名定制欢迎页面 Welcome.jsp  
允许将校验的用户从welcome 页面登出 Welcome.jsp  
可以从标准浏览器中访问,有或者没用JavaScript  Logon.jsp; Welcome.jsp  
导引用户到欢迎页面 index.jsp  

请注意我们在规约中加了一个自定的规约。这是个小技巧,在 Struts 应用中使应用的welcome 页面重定向到一个Struts action。这尽可能将控制流纳入框架,以便有助于在应用增长时最小化改变。

控制器

在一个强壮的分层应用中(见第2 章), 所有对页面和数据的请求请求都传递给控制层。表8 是我们的控制器( “前端控制器” [Go3])的需求。

表格 3-7 控制器规划

规约 ActionForm  
允许在登录页面中输入身份信息(username  LogonForm  
and password)  
Action  
用业务层方法校验身份信息 LogonAction  
向返回返回无效身份信息供纠正 LogonForm;LogonAction  
如果身份信息有效登入用户  LogonAction  
允许经过校验的用户从欢迎页面登出 LogoffAction  
ActionForwards  
向新访问用户提供从欢迎页面的登录  Welcome, logon  
允许经过校验的用户从欢迎页面登出 Logoff  
ActionMappings  
提交身份信息给业务方法校验 LogonSubmit  
Utility  
记录所有内部常数 Constants  

请注意我们自己添加了一个规约 “记录所有内部常数”。这在多层应用中也非常重要,因为这些常数可以“松散绑定”。Java 编译器不能验证我们在XML 配置文件中使用的标志,所以必须仔细跟踪我们所使用的标志。

模型

表格 3-8  model 规划
规约   方法接口
提交身份信息给业务方法校验 boolean isUserLogon(String username,String  
password);  

我们对数据访问层仅有一个需要。

3.4.3. 规划源代码树

现在有了一个基线应用计划,我们可以规划一下应用的代码结构。因为应用很简单,我们可以为页面使用一个单独的子目录,为Java 类使用一个单独的包。

我们的结构如图3-6:

 
图 3-7 代码结构

如果你想跟随我们并建立了你自己的logon 应用, 跳到代码结构和Struts classes 得好办法是部署一个空白的应用:下载一个空白应用。将blank.war 拷贝为logon.war。

将 WAR 放入容器的自动部署文件夹 (通常是webapps). 这既是为什么要提供一个 Blank 应用。它们其实是一个通用的应用模板。我们将在第3.4.4 到 3.4.8 节讨论基本结构。然后再来关注logon 应用。

为了修改和重新部署应用, 需要安装一些开发工具。

3.4.4. 设置开发工具




安装 Ant



安装jEdit



3.4.5. 设置 build.xml文件

象现今的其他一些Java 产品,Struts 也希望使用Jakarta Ant 工具 [ASF, Ant] 作为构建过程的一部分。Ant 也使用一个XML 配置文件, 名字叫build.xml 。通常,你可以为你的应用设置一个固定的build 文件,自始至终不变。在第4 章,我们再表述 logon 应用的构建文件。

3.4.6. 设置web.xml文件

Java 2 Servlet 框架使用一个配置文件来帮助设置应用。

web 部署描述符, web.xml, 标明需要的servlets,以及其他应用设定参数。其格式在servlet 规范[Sun, JST]有描述。大多数 Struts 应用仅需要部署一个单一的servlet 和几个标记库,以便保持相对简单。我们在第4 章表述logon 应用的web.xml 文件。

3.4.7. 设置 struts-config.xml 文件

非常象web 部署描述符, Struts 也有一个XML 配置文件。你的应用在此注册其ActionForm, ActionForward, 和ActionMapping。每个类在文件中都有其自己的一个配置段,在这里你可以定义在启动时需要创建的缺省对象。下面是我们的起始Struts 配置文件:

表格 3-9 配置文件

 
在你配置你的应用时, 你可以从一个空白配置文件开始,就像这个一样,并一步步加入你需要的对象。我们将在本章余下的内容做这个事情,以便你可以看到是如何实际配置Struts 配置文件。第4 章将更深入讨论Struts 配置文件。你可能会注意到我们的起始并不是完全空白的。为方便已经提供了一个缺省welcome forward 。

welcome action


通常, 路由页面流要尽可能的通过Struts 控制器。这样将应用总体设计都保持在Struts 配置之中。这样你可以从一个单一的地方来调整应用的控制流。不幸的是, 容器要求一个物理的welcome 页面。在web 部署描述符 (web.xml) 中列出一个Struts action URI 作为welcome 页面是不允许的。

最好的方案是在一个根index.jsp 文件中将控制重定向到你的welcome action 。struts-blank 就提供了这样一个根。这是一个极其简单页面仅有两行的页面:


空白应用提供 index.jsp 转发页面和一个缺省的welcome 页面。我们将照原样使用index.jsp 但是要对welcome 页面做些改变。然而在开始之前,我们测试一下部署情况。

3.4.8. 测试部署情况

在测试一个新的应用之前确保一切OK, 应该打开一个运行的应用作为基线。Struts Blank 应用是一个基线应用的好选择。其缺省welcome 页面包括了一些基本的系统检测,以看看配置文件是否被正确装入,标签扩展是否能找到,以及消息资源文件是否有效。

Struts Blank 应用的WAR 文件可以在本书的站点或者Struts 发布包中找到。将blank.war 文件放入容器的自动部署文件夹并重新启动容器。然后可以使用这个url 访问应用的welcome 页面:


如果一切OK, 就会看到页面图3-8 一样的页面:


图 3-8 空白应用的欢迎页面


下面是它的源代码:


清单 3.7 空白应用的缺省欢迎页面源代码

3.4.9. 构造欢迎页面

大多数软件开发方法的一个基本原则是尽快的得到一个可以工作的原型。如果你同意这点。那么我们应该做的第一件事就是根据我们的规约构造我们的welcome 页面。这个welcome 页面早期版本, 没有逻辑条件,可能看起来像:

 
清单 3.8 欢迎页面的早期版本

因为这里引用到logon ActionForward ,我们需要将它添加到我们的Struts配置中去。我们也可以改变缺省 welcome 页面从Index.jsp 到Welcome.jsp:


这时,我们可以重新启动容器装入新的Struts 配置。某些容器,象 Tomcat,让你重新装入一个单独的应用。


一旦新的配置装入, 我们可以打开新的welcome 页面:


你将看到图3-9 的屏幕。


图 3-9 登录前的欢迎屏幕


然而,如果你点击连接,你将无法前进,会得到图3-10 的信息。为修正这个错误,我们需要看看下一个目标,构造logon 页面。


图 3-10 文件未找到错误

3.4.10. 构造logon 页面

回去看看3.4.2 的白板, 我们看到logon 页面需要收集username 和 password, 并提交到一个名为/LogonSubmit 的Mapping。这意味着我们需要创建一个Struts form 来标明/LogonSubmit action, 作为一个文本域和密码域的输入控件。下面是logon.jsp 的源代码:


 
清单 3.9 Logon 页面的源代码

标签记引用一个 ActionMapping 对象, 它再引用其他对象(org.apache.struts.action.ActionMapping)。我们先配置ActionMapping, 然后是它使用的对象:


两个相关的对象是 logonForm form bean 和 LogonAction 。我们也需要在Struts 配置中注册ActionForm Bean 。我们使用的名称成为对象在请求上下文中创建时的缺省属性名称。


这时我们需要加入两个特别的Java 类, LogonForm 和 LogonAction 。

3.4.11. 构造Constants类

虽然不是严格要求,但是将ActionForward 名称和其他标记记录在文档中则是强烈推荐的。这其实很简单,而且可以使你的代码易于管理。我们展示代码时,通常忽略了 JavaDoc 说明。但是,在这里我们将留下他们。为什么?因为整个类都是记录常数的类。所以,在这里,文档就是代码。下面是整个类的代码:

 
清单 3.10 Constants 类的源代码

3.4.12. 构造其他类

我们在3.3.8 和 3.3.9 展示了logonForm 和logonAction 的源代码。我们也需要在第1 章中讲述过的UserDirectory 和UserDirectoryException 类。我们可以将他们不经改变的加入到我们的新应用中。在我们的源代码结构中将他们放在/WEB-INF/src/java/app/ 。

 
图 3-11 LogonAction,LogonForrm 以及其它Java 类的位置

LogonAction 也引用 Constants 类。在便以前我们要先加入它。

3.4.13. 创建user directory

在第1 章, 我们引入了一个简单的registration 应用来存储user ID 和password 。这些登陆账号纪录在一个标准的属性文件中,名为user.properties。它可以从那个应用中引入,或者在 WEB-INF/src/java/resources 下重新创建,如图3-12:


图 3-12 用户属性文件的位置

属性文件是一个简单的文本文件。这里是一个简单的例子,使用本书作者的名为用户名,姓为密码。

 
如果你喜欢,你可以只输入这些,或者你愿意输入的内容,并将他们存放在/WEB-INF/src/java/resources/user.properties 。只是保证user ID 全部大写,因为这是业务逻辑中要求的。

当然,你的应用也可以容易的根据一个JNDI 服务,或者数据库来进行校验, 或者使用容器的安全领域。我们在第14 章说明在Struts 中使用数据服务。

3.4.14. 配置ActionErrors
可能记得, LogonForm 和 LogonAction 都要产生错误信息。ActionError 系统是集成在应用消息之中的。

在测试LogonForm 和LogonAction 之前, 我们需要添加这些消息到Application.properties 文档中:


作为构建过程的一部分,我们将应用资源文档从/WEB-INF/src/java/resource 拷贝到classes 文件夹下的资源包,这里ActionServlet 才能找到它们。

3.4.15. 编译并测试logon 页面

在3.4.8 中,我们为logonForm 创建了JSP 页面。但为了使他们运行,我们得添加相应的配置元素和JAVA 类,如表10。

表格 3-10 Logon 页面配置元素


现在我们可以编译应用并测试logon 页面了。有一个存好的build.xml 文件放在 WEB-INF 目录,你可以在Ant 使用它。

按缺省方式构建目标,编译它,将从java 源代码构建 Java 类,并将应用资源消息文件拷贝到classes 目录。


当构建成功,你可以使用下面的链接进到应用中的 logon 页面。 (根据你的容器如何重新装入Java 类,你可能需要重新启动容器。如果不确定,就重启它。)

logon 应用应该可以工作的象我们原来漫游的一样 ( 从3.3.3 到 3.3.6)。所不同的是welcome 页面在用户登录后不会发生变化,或者不提供登出的机会。下面我们将修正它, 然后我们的应用就完成了。

3.4.16. 修改welcome页面

我们先前的welcome 页面忽略关于判断用户是否登陆的条件逻辑。既然,用户已经可以登陆,我们就可以将这些语句加入,以符合3.3.2 中的版本。黑体部分是我们加入的内容:


清单 3.11 welcome 页面 (/pages/Welcome.jsp)的修订版本

如图3-13, 这使我们回到了原来的情形。当用户到来时,他被邀请登录进去。一旦登录进去, 则按名字欢迎它并提供登出的机会。这是你的进步!

3.4.17. Struts ForwardAction Action

如果你注意到你浏览器的地址栏,你可能会注意到我们一直没有暴露JSP 页面的地址。许多应用可能不注意这个方式,但如果你想要严格的MVC 架构(见第2 章), 你可能不希望暴露一些关于你的视图的实现,包括你是否使用JSP 页面或者你如和存储它们。理想情况下,所有的导航都应该通过.do Action 进行,因为他们都通过控制器管理。

当然,并不是所有的事情控制器都能干的很好。这也是为何使用logon 和 welcome 页面。他们并不需要任何来自于model 的信息,直接提供显示JSP 页面的连接。

但这也使用户可以将页面的位置存为书签。接下来,你可能在显示logon 页面之前需要执行一些背景动作,或者你可能需要移动或者将JSP 页面改名。如果用户将这些页面存为书签了,他们就会回到旧的地址,绕开你的逻辑,并返回一个File not found 错误。在实践中, 通常导致将遗留检查放入服务器页面并重定向到web server—事情发生错误的方式更多了,并有更多代码需要维护。

道德?我们必须尽可能虚拟化详细的导航。否则,我们就会不断的为浏览器是否进行缓存或者存储进行补偿。不要直接连接到物理的JSP, 我们应该总是连接到一个虚拟的 Struts Action, 然后由它提供相应的页面。

当然,不管是否需要,为每个页面编写一个Action, 会是一个非常繁琐的工作。一个更有效的办法是,部署一个有用的 Action, 它可以在Struts 配置中定制,并在需要的地方可以重用。因为Struts 为每个Action 创建一个多线程实例,这是一个非常有效的办法,确保控制保留在控制器之中。我们需要做的就是将Action 和 path 传递给页面。

高兴的是,你可以使用struts.jar 中的标准的ForwardAction 对象。你可以简单的目标路径作为ActionMappin 的parameter 属性进行传递:


ForwardAction 将会从mapping 中查找目标路径,并使用它来返回一个ActionForward 给servlet。

实际结果是http://localhost:8080/logon/pages/Welcome.jsp 不会出现在浏览器的地址栏中,那样的话就可以被标为书签直接访问,浏览其中的Action URI 会出现:


用户仍然可以把这个地址记为书签,但是现在你有更多的控制了。你可以logon 活动的实现而不用担心有什么被浏览器记为书签了。之前,你可能不得不考虑如果他们直接访问原来的服务器地址,会发生什么事情。

在MVC 架构中,action 就是你的API。服务器页面是一个实现细节。如果JSP 作为导航系统的一部分暴露给了浏览器,控制器和试图层就混合了,MVC 的优势被削弱。

在需要直接导航至其他页面时,我们也可以加入其他ForwardAction 实例。因为应用仅仅实例化一个ForwardAction, 我们所添加的实际上是一个ActionMapping 对象。如果要求使用ForwardAction 通常的MVC 架构原因还不充分,Struts 1.1 引入的模块化特征要求所有的JSP 请求都要通过Action。这是因为每个模块有其自己的配置上下文并且控制必须通过ActionServlet 控制器传递,以便为JSP 页面选择配置。当然,如果你使用单个的缺省应用,这并不是必须的。但是如果从一开始便遵循这个实践,那么你便可以在不改变任何东西的情况下将你的应用变成一个模块。


3.5. 小结

不管你在开发团队中担任何种角色—工程师, 设计员,架构师,QA—对整个应用是如何运行的有个总体印象总是有帮助的。在这一章,我们综合的解剖了一个小而有用的应用。通过漫游,解剖,然后构造一个logon 应用,我们向你展示了 Struts 框架实际上是如何工作的。作为构造阶段的一部分,我们还创建了一个设计文档,来规划我们的目标、客户需求和程序规约。要使一个应用能运行,我们还构建了应用的 web.xml,Ant 的build.xml 脚本, 以及Struts 配置文件。

为了使正确的构建部件到位,我们按需要的顺序构建了每个组件。在此过程中,我们也指出了最好的实践方法,并强调分离模型、视图和控制器的重要性。

在第4 章,我们会详细探讨 Struts 配置文件。如我们在此所见,配置扮演了一个强有力的角色,使应用易于设计和维护。
发表评论
表情
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
打开HTML
打开UBB
打开表情
隐藏
记住我
昵称   密码   游客无需密码
网址   电邮   [注册]