1、第二章 Struts 入门案例21 从基础了解Struts现在,程序员们已经编写了很多的Web应用程序。正如我们在第1章中指出的那样,使用分层体系结构来编写应用程序可以容易地保证同一个开发小组的成员对不同的部分有专长,同时还可以保证不同的程序员可以同时在应用程序的不同部分并行进行工作。但是如果小组的全体人员都能从头至尾的了解Struts开发的全部流程也是有好处的。在我们开始了解Struts是如何让不同部分组合在一起协同工作前,还是从最墓本例子说起。在本章会使用一个简单但常常被用到的例子用户登录和退出的应用程序,来说明Struts的结构。在介绍了应用程序后,我们会将应用程序分解成不同的模块,并且
2、详细解说每一个模块。如果你已经在你的开发机器上安装了Struts,我们希望你按照本书的介绍一步步完成开发工作。然后,打完根基后,我们会逐渐地开始构造应用程序。每一部分都根据你编写该应用程序的顺序给出,而且会详细到你可以按照它一步步编写该应用程序(对于我们而言,是多么麻烦的输人工作呀!)。如果你是在自己的终端上工作,你可以按照我们的顺序输入全部的源代码。如果不是在自己的终端上工作,你也可以通过阅读本章来了解过程,因为全部的细节在本章中都列举出来了。选择登录程序的原因我们的例子允许用户登录到应用程序中。一旦用户登录进入,则显示的页面会改变以反映出该用户已经获得了使用该应用程序的权限。一般而言,这是
3、用户登录到大型应用程序的第一步,这样应用程序可以使授权的用户从事其感兴趣的动作。但是对于我们而言,用户登录的过程已经足够显示一个Struts应用程序到底是如何工作的。正如表2-1显示的那样,选择用户登录程序作为例子的原因主要是该例子具有如下的特性:容易被理解、简单、自包容以及任何其他应用程序都需要这样的功能,因此我们仅仅选择它作为例子。表2-1 选择登录应用程序的原因原 因解 释容易理解大多数人都曾经登录到某些应用程序中,因此用户登录的流程容易理解简单和自包含一个仅仅允许用户登录的例子程序很简单也容易编写,同时也是自包含的。它不需要复杂的模块很多应用程序都需要这样的模块我们中的大多数人都最终要
4、编写需要某种登录流程的应用程序,因此我们需要该例程22 简述登录应用程序的流程在开始简述前,要先简单讨论一下该登录例子程序的范围以及如何如何遵循这个范围。接着我们会说明该应用程序使用的不同屏幕,同时说明当用户登录时这些屏幕是如何变化的。最后在对本小节进行了简单的总结后,我们会回过头来从整体上对该例子进行分析。2.2.1 起步我们在此处使用登录例子程序的目的是使你体验一下Struts应用程序的具体细节。为了保证我们专注在自己的目的上,本例子程序仅仅包含了用来显现框架结构功能的最基本模块。该例子程序并不包含真正的业务逻辑、单元测试和漂亮的对话框。对于一个真正的应用程序而言,这些都是很重要的,但是对
5、于我们而言,并不重要,因为我们必须在跑前学会走。本例子程序也是一个完完全全的国际化程序。它并没有包含那些可以吸引你眼球的HTML元素它仅仅包含了我们需要的方法。当然,你的Struts应用程序可以做得非常漂亮。如果你期望在你自己的机器上运行该例子程序,这可以在本书的站点上寻找该登录程序Husted。该程序已经打包作为一个可以自动升级的WAR包。尽管我们不要求您运行该应用程序,但是它的运行结果应该还算是有趣的。要运行该程序,你所需要做的事情都已经包含在本章中了。首先,让我们从用户的角度来浏览这些页面。然后,我们再回过头来看看具体的代码。222 将会使用的页面正如表2-2所示,我们的登录应用程序有两
6、个页面:欢迎和登录页面页 面目 的欢迎登录欢迎用户,同时给出应用程序的链接允许输入用户名和密码如果你已经将应用程序配置到自己的机器上,则通过浏览器打开下面的页面就可以看见欢迎页面http:1ocalhost:8080logon。22. 3 欢迎页面第一次访问欢迎页面时,上面仅仅有一个链接。该链接具有“Signin”的标签(图2.1)。当点击该链接后,登录页面就会显示出来。图2-1登陆应用程序的欢迎页面 图2-2登陆页面224 登录页面正如图2.2所示,登录页面会提交用户名和密码给应用程序。检验登录表格是否工作,你可以试试在不输入任何东西的情况下点击提交按钮。则登录页面会再次出现,但是这次会多出
7、一条消息。正如图2.3所示。图2.3登陆页面提示用户名和密码没有输入如果输入了用户名但是忘记了密码,在按下提交按钮后,显示的消息则又不一样了。如图2.4所示。图2-4登陆页面提示你需要输入密码从用户的角度而言,下面是关于该流程中需要记住的一些要点:它立刻告诉用户是否忘记输入某些数据项。如果用户仅仅提交了一个数据,则它会提示用户输入另外一个数据。它在屏幕上再次显示已经输入的数据而不要用户输入Back键。如果用户输入了正确的用户名和密码,则下一个页面会显示。如同第1章中的注册程序一样,本例子程序使用属性文件来检验登录信息的合法性。如果你使用从同上下载的例子程序,则可以用任何一个本书作者的名字来登录
8、进去。如表3-3所示。表2-3 默认的登录用户名和密码用户名密码TedCedricGeorgeDavidCraigHustedDumoulinFranciscusWinterfeldtMcClanahan注意:密码是大小写敏感的。请确保第一个字母是大写。225 再次进入欢迎页面当成功登录后,欢迎页面再次出现。但是这次和第一次看见的欢迎页面有两点不同。如图2.5所示。图2.5首先,页面上会显现用户名。而不是显现“WelcomeWorld!”。而且,页面上还多了一个超链接。除了“Signin”以外,还有“Sign out”。2,26 退出欢迎页面如果点击“Sign out”链接,则我们又回到了最开
9、始的欢迎页面(如图2.1所示)。227 特性摘要尽管我们的应用程序很简单,但是它也显示了几个重要技术:输出超链接。输出表格。校验输入。显示错误信息。重新填充表格。显示可改变的内容。尽管不是显而易见的,但是它还显示了下面的技术:在动态页面中使用图像。重写超链接。在下一节中,我们将通过研究源程序来说明这些核心特性是如何实现的。23 解剖登录应用程序此时我们已经初步知道了如何使用Struts编写登录程序。现在就要回过头来仔细看看该程序的构成。我们将说明每一个页面的代码,同时也说明相关的组件以及每一个部分是如何工作的。在介绍完全部这些细节问题后,我们再说明如果使用这些部分组成一个应用程序。231 浏览
10、器中欢迎页面的代码我们的应用程序是从欢迎页面开始的。现在先让我们看看浏览器中的欢迎页面的代码仅仅是为了看看到底浏览器显示了什么(见代码清单2.1)。下面标注为黑体的部分是显示在屏幕上的文字。代码清单2.1 浏览器中欢迎页面的代码Welcome World!Welcome World!Sign in如果你是Web应用程序的新手,则要记住的一件重要事情就是这儿仅仅只有标准的HTML语句。实际上,Web页面上仅显现那些浏览器能够理解的语言。所有的Web应用程序都受到HTML的限制,因此不能做超过HTML能力范围的事情。Struts通过使用Velocity模板、JSP以及任何我们愿意使用用来编写HTM
11、L的工具来使得该过程简单化。但是我们能够做的事情范围还是要在浏览器能够理解的标志语言范围内。关键字:jsessionid在浏览器源代码中可能有某个东西你认为不是标准的HTML语句。当你第一次该 问该页面时,“sing-in”链接可能看上去实际类似于:Sign in /a大多数Web应用程序都需要跟踪使用该程序的用户。HTTP对维护用户登录提供了一些最基本的支持,但是该方法能力有限,而且也不安全。Java的Servlet技术提供了更好的用户会话支持。但是需要某种机制跨越不同的HTTP请示来维护会话信息。Jsessionid就是容器用来在HTTP上维护用户会话的一个关键技术。在超链接的URL中包含
12、的会话关键字的技术被称为URL重写。Servlet规范Sun,JST鼓励在cookie中维护用户会话。而当cookie不可用时,URL重写技术就应当被使用。当浏览器第一次向容器发出请求时,容器并不知道该浏览器是否接受cookie。尽管容器向浏览器发出一个cookie,但是直到浏览器再次发出请求前,容器是无法辨别该浏览器是否接受了该cookie(HTTP协议没有握手的过程)。同时,当前请求的响应也一定也是要输出的。因此,返回给浏览器的第一页总是会使用URL重写技术。如果后续请求中发现cookie存在,则容器就可以不用URL重写方法。232 欢迎页面的JSP源代码代码清单2-2 欢迎页面的JSP源
13、代码(/pages/Welcome.jsp)Welcome!Welcome !Welcome World!Sign inSign out 现在我们看看那些标记为黑体的行:这些JSP语句等价于Java中的输入语句,并且使输入的扩展标签库可以被页面的其他部分使用。代码会生成一个标准的HTMLbase标签,这使得对图像等资源的引用与原始JSP页面的位置相关。你也许已经注意到,我们的登录应用程序有时候引用do这样的页面。它们并不是真正存在于服务器上的页面,而是引用由程序员编写的Java类或者Action类。这些Action然后会将控制权传递给JSP文件来生成http响应。JSP一般都包含类似于图像或者
14、CSS文件这样资源的引用。最方便使用这些资源的方法就是使用与JSP模板位置相关的路径。但是,当Action将控制转发给JSP页面时,它并没有通知浏览器。因此当浏览器要使用任何具有相对路径的资源引用时,它会将Action所在的路径认为是基本路径,而不是JSP模板所处的位置。根据你访问欢迎页面的时刻不同,浏览器显示该页面的位置可能是:http:/localhost:8080/logon/http:/localhost:8080/logon/LogonSubmit.dohttp:/localhost:8080/logon/Logoff.do对于使用动态内容的应用程序而言,这是一个常见的问题。因此HT
15、ML规范提供了base标签作为解决方法。Struts提供的相对应的html-base标签会将该JSP的位置作为基本路径插入JSP文件中。对于从不同位置生成的登录页面,如果查看对应的HTML源代码,你会发现无论在什么地方,生成的base标签都是: 这样浏览器就可以在发现pages文件夹中的“Power by Struts”图像。现在我们看看下面的代码:Welcome !你可能还记得本欢迎页面是根据用户是否已经登录进入系统来定制化自己的显示。上面的代码像是检查是否在用户session中存储了userbean。如果存在该bean,则在欢迎页面中显现用户名。下面的代码显示了维护用户session的重要
16、性。Struts的标签和Servlet容器会合作来自动维护用户session(不管用户浏览器是否使用cookie)。对于程序员而言,就如同已经在http中内建了session一样这就是框架结构带来的好处。框架结构会扩展底层的支持环境使得程序员可以关注于那些更高层次的任务。Welcome World!相反的,如果存在user bean,则我们会使用定制化的欢迎页面。Struts的逻辑标签都是使用“this”和“notThis”这样的表单,并没有提供类似于“Else”这样的标签。尽管这意味着更多的条件判断语句,但是这也简化了总体语法和标签的实现代码。当然,也可以使用其他的扩展标签库。用户并没有被限
17、制仅仅使用Struts提供的标签库。在Struts网站上的可用资源列表中已经列出了多个共享的扩展标签库ASF,Struts。其中的某个标签库就实现了if/then/else语法。如果你想用这种语法,就可以选用该标签库。正如我们在231节中说的那样,Struts会自动重写超链接来维护用户会话。它也允许你给超链接一个逻辑名,同时将它们存储在配置文件中。这就如同使用关键字来引用数据库中的记录一样。记录中的名字和地址是可以根据需要进行改变的。而应用程序通过使用关键字来找到更新后的记录。在本例中Sign in我们使用logon这个关键字来寻找记录,它存储了使用户登录进入系统的超链接。如果我们以后想改变该
18、超链接,仅仅需要在配置文件中改变一次,然后重新启动应用程序就可以了。页面在被请求时就会自动使用新链接。下面的代码联合使用了 和 仅对已经登录的用户显现退出的链接。Sign out2. 33 欢迎页面的配置信息Struts使用配置文件来定义应用程序中的很多事情,其中就包括超链接的逻辑名。Struts使用的配置文件是一个XML格式的文件,在其启动时会将它读到内存中,并且根据其内容来创建一个存储各种对象的数据库。各个模块都是使用该数据库来提供不同的服务。配置文件的默认名字是struts-configxml。正是因为不同的模块都使用同一个配置文件,因此我们就应该一开始就定义好该文件。现在,我们会逐步给
19、出和每一步相关的那部分配置信息。最后,当我们从头开始构造应用程序时,我们会从整体上了解该配置文件。在开始的欢迎页面中,我们引用了logon。在配置文件中,它是这样定义的:此处,logon是用来寻找某个超链接实际路径的关键字。尽管这儿引用的是Struts的一个Action,但是我们也可以很容易地引用JSP页面、Velocity模板、HTML页面或者带有URl的任何资源。定义URI(uniform resource identifier)是用来定位因特网上或者任何其他计算机网络上资源的短字符串。资源是指文档,图像,可以被下载的文件,电子邮箱或者其他东西。一个URI可以对应服务器上文件系统的路径,但
20、经常是该路径的别名。Struts应用程序中的URI都是Java类或者Action的别名。因为该路径定义在配置文件中,因此你可以在任何时候改变其值而不用修改JSP文件。如果你修改并且重新装载了该配置文件,则再下次生成该JSP对应页面时就会使用修改后的值。234 浏览器中登录页面的代码当你点击欢迎页面中的sign-in超链接,浏览器就会跳转到登录页面。代清单2-3显示了该页面在浏览器中的代码。同样地,下面代码中的黑体部分就是显示在屏幕上的文字。代码清单2.3 登录页面在浏览器中的代码Sign in, Please!Username:Password:代码清单2.4 登录页面对应的JSP代码(pag
21、eslogonjsp)Sign in, Please!Username:Password:我们还是如同处理欢迎页面一样来研究每个黑体部分。首先,和欢迎页面一样,下面的代码使Struts的html标签库可以被页面的其他部分使用:和Struts的action类似,taglib使用的URl也是一个逻辑引用。标签库描述符(tag library descriptor TLD)定义在web.xml中。如果我们没有输入数据时,就提交该表格,并看到一条错误信息。下面的标签就是用来生成该错误信息的。如果没有错误信息,则该标签不会进行任何输出,也不会出现在最终的页面上显示。而标签会生成一个HTML表单来使用户输
22、入数据。它同时还会使用简单的JavaScript来使输入焦点移动到该表格的第一个输入框上。而其action属性指向Struts配置中的某个ActioinMapping对象。该对象决定了使用哪个JavaBean来填充HTML控件。同时该JavaBean也是Struts框架结构的一个ActionForm类。标签创建一个HTML文本域输入框。同时,它还使用该表格对应的JavaBcan中属性名为username的属性值来进行自我填充。Username: 因此,如果该表格因为校验不通过而被返回,同时最后一次提交的用户名是Ted,则该标签的输出就是:否则,该标签就会使用JavaBean中username字
23、段指定的默认值来初始化自己。通常,该默认值是null,但是也可以是任何其他值。同样,标签会创建一个HTML输入控制框。Password:当表格被提交时,需要使用两个对象:ActionForm和Action。程序员负责创建这两个对象并包含应用程序的细节信息。正如图2.6所示,ActionServlet使用配置文件来决定使用哪一个ActionForm和Action。ActionFormActionServlet配置图2-6 配置文件决定使用哪个ActionForm和Action235 登录页面对应的配置部分登录页面本身仅仅使用Struts配置文件中的一个元素:/LogonSubmit。该元素会引用
24、两个元素:app.LogonForm和app.LogonAction。在表2.4中我们依次解释了这三个元素。表2.4 登录页面使用的配置元素元素描述/LogonSubmit封装了构造和提交一个HTML表格给Struts应用程序的实现细节app.LogonForm描述了HTML表格使用的属性app.LogonAction用来完成提交表格流程236 LogonSubmit的源代码在上一节中,我们曾经提到标签和Struts配置文件紧密合作使HTML的表格更加好用:上面的action参数告诉标签使用哪一个ActionMapping。在本例中,对应的ActionMapping如下:表2.5说明了上面每一
25、个元素的意义。正如我们在第2章中提到的那样,Struts使用的很多对象名和属性名都是很含糊的。例如,上面的name属性并不是指定该ActionMapping对象的名字,而是指定该ActionMapping使用的JavaBean或者ActionForm的名字。在配置文件中,同样也定义了form bean:表2.5 ActionMapping的配置元素属 性功 能path是该ActionMapping的唯一标识符。它包含对应的Web地址,例如http:/localhost:8080/logon/LogonSubmitdotype当请求该路径时,调用的Action对象name定义对于一个HTML表格
26、对应的JavaBean(ActionForm)scope该属性定义了存储该JavaBean在请求中还是在会话中validate该属性定义了在调用由type指定的Action对象前是否调用由name指定的JavaBean上的validate方法input该属性定义了当validate方法返回false时控制要转移的地址该元素将逻辑名logonForm和一个指定的Java类app.LogonFonn联系在一起。该类应该是ActionForm的子类。在ActionForm类中已经提供了一些标准的方法,例如validate方法。现在我们先看看LogonForm的源代码,然后再回来看看LogonActi
27、on对象。237 LogonForm的源代码尽管HTML表格允许用户输入数据,但是并没有在应用程序中定义何处来存放这些数据。当用户点击提交按钮时,浏览器收集表格中的数据,然后以名字值成对的形式发送给服务器。因此,当用户在登录页面上输入用户名和密码并且提交其输入后,应用程序知道:username=Tedpassword=LetMeln浏览器将所有的数据都作为字符串进行提交。在页面上通过使用JavaScnpt,你可以强制用户在某些域中输入数字或者某种特定格式的数据,但是这仅仅都是表面现象。用户输入的所有东西都最后会转化为字符串来提交给应用程序,而不是二进制对象。这是浏览器和HTML的工作方式,We
28、b应用程序没有办法控制这一点。类似Struts这样的框架结构已经尽力来进行处理了。Struts使用ActionForm作为解决方案来处理HTTP的数据对。在像Swing这样的环境中,数据输入控制都有一个内置的文本缓冲区来对用户输入的字符进行校验。当用户结束输入离开该缓冲区时,缓冲区中的内容就可以被转换为二进制类型,然后就发送给后面的业务逻辑层进行处理。不幸的是,HTTP/HTML平台并不能提供类似的对输入进行缓存、校验和转换的模块。因此,Struts提供ActionForm(org.apache.struts.action.ActionForm)来弥补Web浏览器和业务逻辑层之间的差距。Act
29、ionForm提供了我们需要的缓存、校验和转换机制来保证用户仅仅能输入期望的数据。当提交HTML表格时,Struts控制器获得提交的名字一值对,然后将它们交给ActionForm来处理。ActionForm是一个和某个HTML表格具有对应属性的JavaBean。Struts会比较ActionForm的每一个属性的名字和提交的表单中名字一值对中的名字。当它们匹配时,这控制器使用相应提交的值来设定JavaBean的属性值。其他的没有匹配的属性则被忽略掉。通常那些被忽略掉的属性则保持其为默认值(一般都是null或者false)。下面是LogonForm的属性:private String passw
30、ord = null;public String getPassword() return (this.password);public void setPassword(String password) this.password = password;private String username = null;public String getUsername() return (this.username);public void setUsername(String username) this.username = username;大多数ActionForm类的属性都这样。一些程
31、序员使用宏就可以容易地通过输入属性名来生成对应的代码。另外一些可以在编辑器中使用代码粘贴。而Struts代码生成器则可以通过使用HTML或者JSP来创建对应的ActionForm。注意 在Struts11中,如果使用DynaActionForm或者集合类型的ActionForm,则创建ActionForm会更加简单。基本的ActionForm包含两个标准方法reset和validate。在向导(wizard)工作流中使用ActionForm时,reset方法很有用。如果该对象工作的范围被设定为请求(request),则可以使用默认的实现。当validate被设定为true时,则在该bean通过
32、HTTP请求进行填充后会调用它的validate方法。Validate方法经常被用来进行主要的校验工作。该校验方法仅仅检查数据是“看上去”正确,并且所有需要的值都被提交。再重复一次,对于Swing控件而言,这些工作都是swing,组件在将数据传递给应用程序前内部进行的操作。对于使用Struts的应用程序而言,我们也可以手工进行这些操作,或者通过使用ValidatorForm来实现,它也可以通过配置文件进行设置。下面就是LogonForm的validate方法。它仅仅检查其对应的两个域是否都输入了数据。如果应用程序有其他的要求,例如用户名或者密码的长度有限制,则你可以在该方法中进行检查。publ
33、ic ActionErrors validate(ActionMapping mapping,HttpServletRequest request) ActionErrors errors = new ActionErrors();if (username = null) | (username.length() 1)errors.add (username,new ActionError(error.username.required);if (password = null) | (password.length() 1)errors.add(password,new ActionErro
34、r(error.password.required);return errors;该方法返回的ActionError是Struts框架结构提供的另外一个类。如果vslidate方法返回的ActionError对象既不是null也不是空,则控制器通过一个已定义的键将该对象保存在HTTP请求的上下文中。因为标签也知道该键,所以如果该键存在,那么该标签会将错误信息提取出来。如果在HTTP请求中不存在该键,那么该标签什么也不做。Error.username.reqmred和error.password.required都是键。它们用来从Struts消息资源文件中提取真正的消息。每一个地区,都有一个自己
35、的资源文件。这就简化了消息的本地化。Struts的消息资源文件同样使用名字一值对。在我们的例子中,对应的两项是:Error.Username,required=Username is requxrederror.password.required=Passuord is renuired注意 在Struts11中,还有其他的方法在消息中使用HTML标签。可以使用errorsprefix和errorsuffix的新特性来指定类似于这样的标签。同时,也可以使用新的消息标签来代替原有的标签。该标签简化了表现层上标签的维护工作。就算不需要进行本地化工作,Struts应用程序将全部消息存储在一个地方,这样用户就可以在不修改JSP代码的情况下对这些消息进行浏览和修改了。238 LogonAction的源代码在将数据收集进ActionForm对象,并且进行了初步的校验后,控制器将ActionForm对象传递给指定的Action类。Struts应用程序认为你会使用自己的类来完成绝大部分请求工作。Action会保存请求完成后的结果,而JSP文件是用来显示该结果的。正如我们在第2章中阐述的那样,这就是被称为MVC或者Model2的解决方法。其中,Action类的工作就是分派HTTP请求。当Struts的sevrlet接受到某个Acti