Jakarta Struts简介
翻译作者:-
Matrix开源技术-Wingc
翻译前的话 呵呵,这是wingc第一次真正意义的翻译技术文章,自己看E文文档和把E文翻译成能读的中文那真是两回事啊,翻译得不好再所难免,您可别骂wingc哦:)若有什么地方不通、不爽或不妥,请见
http://www.onjava.com/pub/a/onjava/2001/09/11/jsp_servlets.html看原文吧。
Web应用开发早期曾经是那么的“简单”,那个时候还只是纯HTML页面和浏览器特效而已。由于还涉及不到动态数据操作和商业应用,也就省去了很多麻烦。但是这样的“简单”只是过眼云烟,如今我们不得不为复杂的基于Web的商业应用开发采用诸多技术。
本文将介绍如何利用Struts进行应用开发的前台整合的开发过程。Struts是一个为开发基于模型(Model)-视图(View)-控制器(Controller)(MVC)模式的应用架构的开源框架(译注1),是利用Java Servlet和JSP构建Web应用的一项非常有用的技术。
阅读本文需要读者具有以下几方面的开发经验:JSP、Servlet、自定义标签库(custom tag library)和XML。如果读者想补一补自定义标签库的知识,可以参考作者以前关于这方面的文章。而本文也是关于介绍如何使用Struts系列文章的上半部分,本系列暂定分为上下两部分。
新手上路注意事项 Struts是一个基于Sun J2EE平台的MVC框架,主要是采用Servlet和JSP技术来实现的。其最初萌芽于Craig McClanahan的构思,诞生至今也一年有余了(译注2)。现在,Struts是Apache软件基金会旗下Jakarta项目组的一部分,其官方网站是
http://jakarta.apache.org/struts。由于Struts能充分满足应用开发的需求,简单易用,敏捷迅速,在过去的一年中颇受关注。Struts把Servlet、JSP、自定义标签和信息资源(message resources)整合到一个统一的框架中,开发人员利用其进行开发时不用再自己编码实现全套MVC模式,极大的节省了时间,所以说Struts是一个非常不错的应用框架。
目前的Struts 1.0修正版包括完整的文档,既可以说是用户文档又是开发指导文档。如果读者是JSP新手,或者对MVC设计模式不是太熟的话,可能刚上路时会比较慢,不过不用担心,要相信自己会尽快赶上的:)
此外,应该注意到尽管当前Struts只是1.0版,但已经相当稳定了,作者从Struts 0.9版就在一个大规模的项目中应用了(最近升级到1.0版),至今还没有遇到什么麻烦问题。实际上,Struts在这个要开发复杂用户界面的项目中,为我们团队大大的缩短了开发时间,在此衷心的感谢Struts项目团队的所有开发人员。
哦,还有,如果读者开始上路了,要知道Struts的邮件列表可是有相当分量的,在这里混混才可保证能及时跟上Jakarta项目的最新动态哦
http://jakarta.apache.org/site/mail.html。
开始上路! Struts框架可分为以下四个主要部分,其中三个就和MVC模式紧密相关:
1、模型(Model),本质上来说在Struts中Model是一个Action类(这个会在后面详细讨论),开发者通过其实现商业逻辑,同时用户请求通过控制器(Controller)向Action的转发过程是基于由struts-config.xml文件描述的配置信息的。
2、视图(View),View是由与控制器Servlet配合工作的一整套JSP定制标签库构成,利用她们我们可以快速建立应用系统的界面。
3、控制器(Controller),本质上是一个Servlet,将客户端请求转发到相应的Action类。
4、一堆用来做XML文件解析的工具包,Struts是用XML来描述如何自动产生一些JavaBean的属性的,此外Struts还利用XML来描述在国际化应用的用户提示信息的(这样一来就了应用系统的实现多语言支持)。
好,下一步咱们来看看构成这个框架的各个部分以及相互之间是怎样运作的吧!
搞定配置先 在使用Struts之前,咱们必先设置好JSP服务器,以便让服务器在用户请求时,知道该如何将指定后缀的请求转到相应的Controller-Struts ActionServlet处理,当然,这些配置信息都一般在服务器启动时通过web.xml文件读入的。我们可以在web.xml定义多个Controlloer,为每一个应用定义一个。一个典型的web.xml文件配置如下,其中有相应的注释,很好懂的,在后面讨论Action的时候,我们将主要分析strutc-config.xml。
<web-app>
<servlet>
<!--
以下配置信息声明了Struts中的ActionServlet,即一个名为OreillyAction的Servlet,其具体实现为org.apache.struts.action.ActionServlet。在这个配置中还有这个Servlet的两个参数:debug level和detail,此处这两个参数的值都设为了2,此外还设置了在启动载入时创建两个实例。
-->
<servlet-name>OreillyActionServlet</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!--
设置所有后缀为.action的请求,都转发到OreillyActionServlet
-->
<servlet-mapping>
<servlet-name> OreillyActionServlet </servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<!--
将初始请求页面设置为login.jsp
-->
<welcome-file-list><welcome-file>login.jsp</welcome-file></welcome-file-list>
<!--
设置Struts的JSP页面要用到的标签库和她们的路径
-->
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</web-app>
控制器(Controller) Controller是这个框架中扮演“交通警察”的角色,当客户端与服务器有交互动作时,都由她来控制。Controller将HTTP请求封包并转发到框架中相应的对象,这些对象可能是一个JSP页面或一个Action。
Controller在web.xml中设置为org.apache.struts.action.ActionServlet的一个实例,在本例中,这个实例就是OreillyActionServlet。在一个完整的控制过程中,也就是处理一个HTTP请求时,在控制过程之初,这个Servlet会从一个配置文件struts-config.xml中获取请求与控制动作向对应的配置信息,这个我们会在后面详细讨论,Controller通过这些配置信息来决定HTTP请求该往何处转发,而这些Action在接收到转发来的请求后,实现真正的商业逻辑。我们要注意的非常重要的一点是Action对象要能够调用这个ActionServlet的若干方法,通过这个有力的特性,当Action对象在控制过程中将请求再向别的Action对象转发时(最初的请求是由ActionServlet获取,向Action对象转发,而Action对象还可以再转发到别的对象),我们可以将一些需要共享的数据对象通过调用一些方法放入这个Servlet相关的一些标准容器中捎带过去。
模型(Model) 所谓Model就是在对用户请求的整个控制过程中,真正处理用户请求并保存处理结果的对象,在整个过程中,我们一般利用JavaBean来把一些信息保存起来以便在各个对象之间传递。因为在框架中,Model对象是真正处理商业逻辑功能的对象,因此也就是框架中应用需求实现相关性最大的部分。在Struts的实现里,Model的具体表现形式就是ActionForm对象和与其对应的Action对象了。对用户提交表单的数据进行校验,甚至对数据进行预处理都能在ActionForm中完成。通常的应用中,一般是一个Model对象和一个请求页面对应的关系,但也可以一个Model对象对应多个页面请求。如果struts-config.xml配置文件没有指定一个Model对象对应的Action,那么控制器将直接把(通过模型对象完成数据封装的)请求转到一个View对象。
struts-config.xml 前面多次提到的struts-config.xml配置文件是整个框架的主心骨。web.xml文件定义了一个请求到来应向何处转发后,后面的工作就全权由struts-config.xml管理控制了。可以说struts-config.xml就是整个Struts框架的“扛把子”(译注3),只有这位“老大”清楚所有请求与动作的映射关系,要是他哪里没有搞定或不爽的话,整个“社团”就什么也摆不平了:)如今的应用系统,XML形式的配置文件越来越多,如果整个系统只使用一个这样的配置文件的话,那么保持整个系统的模块化和可维护性都非常的轻松。使用配置文件来描述请求-动作的控制过程和相互关系,而不是在代码中将对象之间的调用关系写死,那么都应用系统有变动时,我们只用修改配置文件就行了,而不是再重新编译发布程序了。
Controller通过struts-config.xml文件的配置信息确定当有请求时应该调用那个对象来处理,从效率的角度出发,这些信息都是在系统启动时读入并存在内存中的。下面我们将讲解一个极短小的struts-config.xml文件,文件中定义了一个与登录请求对应的登录动作,请求到达后将被转发到com.oreilly.ui.authentication.actions.LoginAction这个Action对象,该对象处理的结果决定向用户返回的页面。这个例子同时还示范了一个Action对象将请求转发到别的Action对象,而例子中另一个返回的对象则是一个View对象,即我们看到的login.jsp页面。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.0//EN"
"
http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd";>
<struts-config>
<!-- ========== Action 映射定义 =================== -->
<action-mappings>
<!-- <action> 属性的说明 -->
<!-
type - 完整的Action实现类名
name - 该Action要用到的ActionForm名
path - 请求该Action的URI
validate - 如果本属性为true则在Action动作之前其对应的ActionForm的validate方法会自动被调用,一般用以校验用户输入的数据
-->
<!-- ~~~~~~~~~~~~~~~~~~~~~ -->
<!-- O'Reilly Main Actions -->
<!-- ~~~~~~~~~~~~~~~~~~~~~ -->
<action path="/Login"
type="com.oreilly.ui.authentication.actions.LoginAction">
<forward name="success" path="/DisplayMainPage.action"/>
<forward name="failure" path="/login.jsp"/>
</action>
</action-mappings>
</struts-config>
视图(View) View对象通常来说都是指的JSP页面。Struts框架实际上并没有真正的JSP的要求规范,而是提供了大量的标签库来为开发者更简便的将JSP整合到Struts框架中。在Struts中通过标签定义方式的JSP页面能够将用户通过表单输入的数据存入一个JavaBean中,也就是我们前面提到的ActionForm bean。通过Action类调用(自动或手动)ActionForm的校验方法来检查用户输入的数据,如果发现不合法的数据,再通过Struts的一个通用机制将错误信息返回给用户显示。
Struts框架提供了若干个标签库,它们有各自不同的用途。由于这些库还可以脱离Struts框架单独使用,这样我们也可以在其他系统中尝试使用这些标签库,它们包括:
* struts-html - 这个标签库用来创建动态的HTML页面和表单。
* struts-bean - 提供了类似甚至更强于<jsp:useBean>中的功能。
* struts-logic - 用于在页面输出文本信息时的条件、循环等流程的控制。
* struts-template - 用于产生有共同风格的动态JSP页面模板。
此外,可定制标签库在Struts中还有一大用处是,通过资源文件的方式来实现应用系统的多语言特性,应用Struts的系统若想将系统中的用户交互信息换一种语言的会很简单,更换一个不同的资源文件就可以了。
大家都开始应用Struts吧! Struts框架可能对于大多数开发人员来说,是一门比较新的技术。但我们现在已经可以在不少的应用系统中看到Struts的身影了,而我们大可在新的应用或正在开发的JSP项目中使用Struts框架。
例如,在作者现在正在为客户开发的一个大型数据库应用系统中,商业逻辑都是通过EJB来实现的,用户界面则是JSP页面。在struts-config.xml文件中定义了用户输入表单和对应的Action类,当一个请求发生时,即用户数据以ActionForm的形式封装提交到Action时,Action先调用ActionForm的校验方法,数据检查校验通过后,Action再调用相应的EJB中的方法来完成数据操作,操作的结果以XML的形式返回,XML解析后再放入我们数据的封装传递JavaBean - ActionForm中显示到JSP页面里返回用户。
整个的控制流程(包括Action调用后的不同的返回结果)都尽在struts-config.xml中所掌握,这种“中央集权”的方式非常便于应用流程的调整。而不管是Servlet还是JSP页面中(甚至在一些n层的应用架构)都无需撰写如何获取显示数据的代码。
由于目前作者所开发的是一个较大型的系统,有很多的JSP页面和用户要提交的ActionForm类型,因此发现Struts的一个麻烦的地方,那就是:我们要为如此多页面和ActionForm开发对应的Action类来完成控制,因为我们目前JSP和ActionForm与Action是一对一的关系。不过我认为如果在项目前期分析和设计时多下些功夫,做出更完美一些的设计方案的话,这样的情况是可以避免的,当然,在新产品的开发过程中,想一步就把所有需求弄清楚明白那也是不可能的。我们不是都有这样的经历吗?在开发中的应用系统正一步一步走向成熟的时候,更新和更明确的需求才会被提出来。不过,像我们手里这个利用Struts开发了六个月的系统也确实少见了,呵呵。除去这些非技术因素不谈,Struts框架为我们实现MVC模式节省了大量的时间,并且开发出的系统相当的稳定,可以说是很成熟的产品了。
在本系列文章的第二部分,我们将把各小段代码集成起来,完成一个完整的Struts应用的实例,希望大家继续和作者一起学习Struts!
Sue Spielman是ONJava.com的副编辑,主要擅长于JSP和Servlet技术,她还是Switchback Software LLC公司的总裁和高级技术咨询专家。
译注1:虽然常见将Controller、Model和View这三个翻译为对应的“控制器”、“模型”和“视图”,但总感觉在表达上还是有欠本意,所以译文中只在需要名词定义的地方将三个中文词汇写上,文中其他的发还是保留E文“真身”。
译注2:这是2001年9月11日(没错,就是“911”当天哦)发的文章啦,要知道文章里的“那时”也是现在的好久之前的时候了。
译注3:原文中此处是用“Vito Corleone”来形容struts-config.xml的,知道唯托.科尼奥尼是谁吗?呵呵,经典电影《教父I》和《教父II》里的教父啊,马龙.白兰度和罗伯特.德尼罗先后塑造的经典形象,所以wingc在这里也用了一点江湖词汇,请勿见怪。
学习Jakarta Struts(第二篇)
本文是三篇学习Struts框架系列文章的第二篇(原文请见
http://www.onjava.com/pub/a/onjava/2001/10/31/struts2.html)。在本系列的的第一篇中,我们大致浏览了Struts框架,框架的功能以及框架中应用到的各个模块。而本文,我将利用Struts 1.0为大家演示建立一个简单的应用;在第三篇文章中将介绍如何利用Struts的标签在JSP中访问ApplicationResource文件中的信息。
我们在这篇文章将会一步一步的讲解Struts的应用,以这样的形式打开Struts一道道神秘的大门,通过这样的过程,相信也能激起你在应用开发中如何应用Struts的灵感。如果你对Struts的一些术语不是很清楚的话,可以参考本系列前一篇对Struts作大体介绍的文章。
再次重复一遍,本文需要读者有如下几方面的知识和经验:JSP,Servlets,自定义标签库(Custom Tag libraries)和XML。此外,在本文中,我还会用到Jakarta项目组其他一些好东东,比如Tomcat
http://jakarta.apache.org/tomcat/index.html(实现Java Servlet和JSP官方标准的Servlet容器,通俗的讲就是一个JSP的Web Server啦)和Ant
http://jakarta.apache.org/ant/index.html(基于Java的自动编译发布工具,这可是好东东啊)。
作为一名一直使用前沿技术开发了诸多应用的技术人员,我一直坚信掌握新技术,理解该技术开发的逻辑是至关重要的。但这往往就是陷住我们学习步伐的泥潭,正因如此,我打算将利用Struts开发的一套完整流程作为我们教学的案例。该流程的这个案例可谓“麻雀虽小、五脏据全”,你完全可以将这个流程应用到你手头那些复杂庞大的项目中,至少在我们的大项目中应用这个流程效果不错。
有开发复杂商业应用的开发人员都知道,客户的需求总是在不停变幻,所以如果有一套规范的开发流程来遵循,当客户提出新的需求时,我们至少可以明确哪些“无理”需求其实是合理可行的。好,接下里我将在我的这个例子中向各位展示和应用整个流程。
本文中的示例代码是StrutsSample应用中的一部分,包括build.xml的完整代码可以到此处
http://www.onjava.com/onjava/2001/10/31/examples/StrutsPartII.jar下载。
Struts开发过程 从Struts发布的版本号可以看出,Struts是个新玩意,她有好几个部分组成,明智的你如果搞清楚了何时该开发完成合适的部分,那将会更好的利用我们的开发时间。从我所开发的几个利用Struts应用中,我大致总结出如下这个比较有效的开发步骤:
1,明确应用需求;
2,由用户输入和获取数据的角度出发,明确和设计出每一个用户界面;
3,确定用户界面的进入路径;
4,由应用逻辑信息确定动作映射表(ActionMapping);
5,由设计完成的用户界面开发其所用到的类和应用函数;
6,由用户界面中的数据信息开发ActionForm和相应的数据校验方法;
7,ActionMapping中将会被调用相应的Action或转到相应的JSP页面,这一步我们先开发这些Action;
8,开发商业应用逻辑,就是相应的JavaBean、EJB或其他东东;
9,开发由ActionMapping定义的系统工作流程完成对应的JSP页面;
10,完成系统配置文件:struts-config.xml和web.xml;
11,编译/测试/发布。
明确应用需求 开发任何应用系统的第一步就是收集用户需求信息。不管一个用户逻辑初看上去多么合理,但总有可能在开发时才发现它比看上去要难得多。所以,建议拟一份明确的用户需求列表,这不只是出于开发的目的,还能通过该表分析用户需求以确定哪些地方可能需要花更多的精力。
在我们这个StrutsSample项目中,应用需求就是:
作为一个展示Struts框架应用的完整例子,本示例完成的功能是用户登录。目的只为明确Struts的应用,本示例将不会涉及到一般复杂应用系统中可能应用的安全、数据库、EJB开发等等相关技术。
设计用户界面 这个应用中,包括如下三个用户界面:
1)登录界面,用于用户名和密码输入;
2)当登录用户为合法用户时的欢迎界面;
3)当登录失败时的错误提示界面。
确定用户界面的进入路径1)登录界面作为这个应用的默认页面;
2)欢迎界面只有当成功登录后才能进入;
3)任何可能发生错误的页面能可以进入错误提示界面;
由应用逻辑信息确定ActionMapping ActionMapping为整个应用确定的“线路图”,在配置文件struts-config.xml对ActionMapping进行定义,通过转发请求(forward)来理顺应用的处理流程,确定应用中每个用户请求对应的动作。
通常我们在开发过程中就逐步确定了ActionMapping所需的信息,开发代码的过程就是在由草稿开始一步步完善struts-config.xml的过程。当Action类处理完用户请求后,其返回的的forward就是在ActionMapping中定义的一个。一个Action返回的forward完全有多种可能,尽管一个Action一般只定义其相关的几个forward。那么,如果有多个Action都可能返回的同一个forward,那么就可以将其定义为全局转发(global forward)。这类似于C中的头文件中全局变量,如果在struts-config.xml描述信息中,某一个forward并不是在当前Action描述中定义的而是全局定义的,那么这个全局的将起作用,同样,一个Action中当前定义的forward将覆盖全局定义。在我们所给的这个简单实例中,我们定义了全局forward――“error”,当某Action返回的forward是“error”这个映射,那么Errorpage.jsp页面将会显示给用户,尽管当前Action并没有对其定义。
我们继续不断的开发,项目日渐完善,项目相关的配置文件也会越来越详细。在下面的例子中,我们将以StrutsSample中用到的struts-confug.xml文件为例,学习global forward和一个Action中相关映射的定义。下面定义了一个名为“login”的Action,其为com.oreilly.actions.LoginAction的实例,当Action处理用户登录成功后将一个名为"success"的forward返回,用户也就会看到Welcome.jsp页面,如果登录失败,Action将返回对应的forward以再显示Login.jsp给用户,而如果处理过程中发生其他错误,Action将返回全局定义的forward――“error”,用户也就会看到错误提示页面Errorpage.jsp。
<!-- ========== Global Forward 定义 -->
<global-forwards>
<forward name="login" path="/Login.jsp"/>
<forward name="error" path="/Errorpage.jsp"/>
</global-forwards>
<!-- ========== Action Mapping 定义 -->
<action-mappings>
<!-- <action>元素的相关属性 -->
<!--
以下只列出常用属性,其他请参考org.apache.struts.action.ActionMapping的相关文档
path - 当前Action对应的用户请求URI路径
type - 实现当前Action的Java class的完整名字
name - 当前Action中用到的ActionForm的名字,其具体信息在配置文件其他地方另有详细定义
unknown - 如果将该属性设置为true,那么就是声明这个Action将处理整个应用中所有未找到相应处理Action的请求,当然,一个应用系统中也只会有一个Action的unknown属性可以设为true
scope - Action中所用到的ActionForm的生存期,可以为“request”或“session”,随着生存期的设置,该Action也会在相应的时间被创建
input - 该Action中相关ActionForm获取用户输入的输入页面,当将ActionForm设为自动验证输入数据,发现不合法数据返回错误时,将返回该页面
validate - 如果本属性为true则在Action动作之前其对应的ActionForm的validate方法会自动被调用,一般用以验证用户输入的数据
forward 元素 - 定义当前Action相关的ActionForward
-->
<!-- =================== -->
<!-- O'Reilly Struts Sample Main Actions -->
<!-- =================== -->
<action path="/login"
type="com.oreilly.actions.LoginAction"
name="loginForm"
scope="request"
input="/Login.jsp">
<forward name="success" path="/Welcome.jsp"/>
<forward name="failure" path="/Login.jsp"/>
</action>
</action-mappings>
在前一篇文章中,我们曾说过,struts-config.xml就是MVC模式的的Controller。在确定struts-config.xml中的配置信息时,应该多花些时间精力在上面,以保证每一个Action定义及其相关定义是符合应用的需求的。如果在项目开始没有详细的设计其定义,当将所有代码和配置集成到一起的时候,我们将不可避免的将各部分的代码和配置完全重新组织一遍。
我们当前的例子StrusSample因为只是处理用户登录,所以只需要一个Action。一个应用系统中所要用到的Action的多少完全依应用的大小而定。一旦整套Action的映射完全的定义出来后,我们就可以一个一个开发其具体实现的Action和ActionForm类,并逐渐将完成的部分一点一点集成起来。
由设计完成的用户界面开发其所用到的类和应用函数 所有ActionForm的实现类都是org.apache.struts.ActionForm的子类。一个ActionForm是与页面上的输入表单相关联的,而且ActionForm的实现还可以对用户输入数据的合法性进行验证。作为一个Java Bean,ActionForm有Set和Get方法,当一个页面中表单被提交时,系统将自动调用Set方法将数据放入ActionForm中,而Get方法将为在Action中操作这些数据所提供。一般来说,处理表单中的所有数据,并进行合法性验证都完全可以交由ActionForm来完成。在应用中,就我个人而言,倾向于将ActionForm和Action划分到不同的包中,因为当一个页面中要用到几对ActionFrom和Action时,都放在一个包内会混淆的。下面的代码,就是实例中登录页面用到的ActionForm的代码。
/*
* LoginForm.java
*/
package com.oreilly.forms;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
/**
* 验证用户要用到的两个数据
*
* username - 登录用户名
* password - 用户密码
*
*/
public final class LoginForm extends ActionForm {
private String userName = null;
private String password = null;
/**
* userName的Get方法
* @return String
*/
public String getUserName() {
return (userName);
}
/**
* userName的Set方法
* @param userName
*/
public void setUserName(String newUserName) {
userName = newUserName;
}
/**
* password的Get方法
* @return String
*/
public String getPassword() {
return (password);
}
/**
* password的Set方法
* @param password
*/
public void setPassword(String newPassword) {
password = newPassword;
}
/**
* 重置所有数据
*
* @param mapping 当前的ActionMapping
* @param request 当前Server正在处理的HttpServletRequest
*/
public void reset(ActionMapping mapping, HttpServletRequest request) {
userName = null;
password = null;
}
/**
* 验证当前HTTP请求提交上来的数据
* 如果数据验证发现不合法数据,将返回一个封装
* 所有验证错误的ActionErrors对象
* 如果数据验证通过,该方法返回null或者一个
* 没有封装任何验证错误的ActionErrors对象
*
* @param mapping 当前的ActionMapping
* @param request 当前Server正在处理的HttpServletRequest
*/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
// 当前ActionForm中,只需要检查用户输入的用户名数据
if( userName == null || userName.length()==0 ){
errors.add("userName",new ActionError("error.userName.required"));
}
return (errors);
}
}
以上的代码,只有两点和一般的Java Bean有所不同。其一是reset方法,方法中设置的值将在表单被reset时反应到其对应的表单项上,即将表单项的数据恢复到默认值。其二是validate方法,是用来验证用户在表单中所输入数据的方法。在当前这个例子中,我们只验证用户输入的用户名。因为一个用户名其对应的密码可能为空,所以我们的逻辑就是验证时不去检查密码。验证用户名,当发现输入的用户名为空时,方法就会产生一个错误对象(ActionError)。
在Struts中用ActionErrors来装载多个错误,从ActionErrors结尾的那个“s”就可以知道她是一个ActionError对象的集合。在验证用户输入时,可以验证完表单中所有数据后,再将可能发现的多个错误通过ActionErrors返回给用户,这样的逻辑应该是想当然的啦,不可能用户有五个不同的输入错误,却要分五次提示,让用户修改提交五遍吧,呵呵。
同时,要知道在我们这个例子中,我们将错误信息提示给用户是通过ApplicationResource.properties文件。这个文件在Tomcat启动时通过web.xml中的定义为这个应用所使用。通常每一个应用都在其WEB-INF目录下都有web.xml文件来描述系统,而关于部署应用时具体的结构信息,请参考Tomcat
http://jakarta.apache.org/tomcat/index.html等Server相关的用户手册。
ApplicationResource.properties文件中可以定义应用中所要用到的提示信息的字符串,字符串都通过一个键值来唯一确定其位置。在我们这个例子中,键值error.userName.required所对应的字符串信息是“A username is required”,在给用户显示错误信息时,也就通过键值确定的错误提示显示该字符串。通过这样的机制,为我们在系统中实现多语言提供了便利,将该文件中的这些字符串翻译成相应的语言,我们的系统就可以实现西班牙语、德语、法语和汉语等等语言的版本了。
下面这些ApplicationResource.properties中的信息对于我们这个简单的示例中已经够用了:
login.title=Login Struts Sample
error.userName.required=A username is required
error.login.authenticate=Invalid username/password
errors.footer=</ul><hr>
errors.header=<h3><font color="red">Page
Validation</font></h3>Please correct the
following error(s) before contiuing:<ul>
applicationResources=Cannot load application resources bundle
{0}
页面的标题,按钮或其他什么需要文本提示的地方都可以通过这个文件来定义显示用字符串。我们将在该系列的最后一篇文章,也就是后续几个开发步骤中讲解如何通过Struts的标签从这个文件中获取显示用字符串。
学习Jakarta Struts(第三篇)
本文是三篇学习Struts框架系列文章的最后一篇(原文请见
http://www.onjava.com/pub/a/onjava/2001/11/14/jsp_servlets.html)。
在第一篇文章《Jakarta Struts简介》中,我大致分析了Struts框架,讨论了它所能完成的功能,还浏览了组成Struts的各个组成部分。在第二篇文章《学习Jakarta Struts》中,我开始详细描述如何利用Struts来构建一个简单应用的过程步骤。而本篇文章将会向大家演示如何将ApplicationResource文件中的文本信息,通过Struts标签在JSP页面中显示出来。
Action类是连接Struts架构和应用中业务逻辑代码的桥梁。所以你应该尽可能让Action类小巧简单,因为真实应用中的逻辑处理应该是由单独分离出来的逻辑层来完成的。如果你正在从事n层应用的开发,你当然希望层与层之间的接口越简单越好。而事实上,Action类中的主要方法"perform()"(1.1中为execute())却有点暗示应该在本方法中做点什么的意思。我们知道,每个Action类都需要从 org.apache.struts.action.Action 继承而来。在小型应用中,我们的Action类很可能就只要继承org.apache.struts.action.Action就足够了;而在某些特定的复杂应用中,我就从我们所实现的Action类中总结出来了一些通用特性。因此,在我看来,构造一个基类将这些通用特性的代码实现出来,让应用中所用到的所有Action类不直接继承org.apache.struts.action.Action,而继承这个完成了一些通用特性的基类以实现代码重用,是一个相当不错的设计。我在StrutsSample中就应用了这种方法,构造了这样的一个基类,该基类的方法在完成复杂逻辑的和简单转发请求的Action类中都可以使用。
package com.oreilly.actions;
import java.io.IOException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Enumeration;
import java.util.Properties;
import java.rmi.RemoteException;
import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
这个类就是使用Struts开发时,所有Action类都要继承的基类。它把一些通常在实际应用中最有可能被用到的东西都考虑进来了。就这篇文章而言, 类中一些与Struts并不是太紧密相关的方法将只做注释而不会完整的实现,而从事开发工作的你,有兴趣的话,请完成这些方法并应用这个类,将为你在实际项目中的开发快马加鞭。注意,因为所有的Action类都要从org.apache.struts.action.Action 继承而来,所以我们的这个类同样。
public abstract class AbstStrutsActionBase extends Action {
/**
* 定义一些在struts-config.xml中记录在案的
* 全局应用中皆可可通用的forward标识
*/
protected static final String SUCCESS = "success";
protected static final String FAILURE = "failure";
protected static final String ERROR = "error";
protected static final String LOGIN = "login";
protected static final String CONFIRM = "confirm";
protected Context jndiContext = null;
/**
* 默认构造方法
*/
public AbstStrutsActionBase() {
}
/**
下面这个查找EJB实例的方法将不会完整实现。
一般来说,Action类应该调用实现了应用的商务逻辑的EJB会话bean(或仅仅普通JavaBean)。在大型项目中,开发人员必须划清层与层之间的界限。在Action类中,我们应该拿到获取含有JNDI信息的环境的实例,然后通过EJB的JNDI名字去查询获取它的home接口。过程并不简单,所以下面这个代码片断只是个给出了必要实现的小例子。
参数类型String,传入的要查询JNDI的名字
返回类型Object,即查找到的home接口
如果查找失败,抛出NamingException异常
如果获取资源信息失败,抛出MissingResourceException异常
*/
public Object lookup(String jndiName)
throws NamingException, MissingResourceException {
// 为调用EJB对象,通过构建记录JNDI信息的Properties对象
// 来获得初始环境信息
if (jndiContext == null) {
ResourceBundle resource =
ResourceBundle.getBundle("strutssample.properties");
Properties properties = new Properties();
properties.setProperty(
Context.INITIAL_CONTEXT_FACTORY,
resource.getString(Context.INITIAL_CONTEXT_FACTORY));
properties.setProperty(
Context.PROVIDER_URL,
resource.getString(Context.PROVIDER_URL));
properties.setProperty(
Context.SECURITY_PRINCIPAL,
resource.getString(Context.SECURITY_PRINCIPAL));
properties.setProperty(
Context.SECURITY_CREDENTIALS,
resource.getString(Context.SECURITY_CREDENTIALS));
jndiContext = new InitialContext(properties);
}
注意:在真正的产品中,我们应该在此处考虑代码的健壮性,将代码加入到try/catch块内,并记录所有错误或重要信息到系统log中。而本例中,我们仅仅把异常往外抛,并假定一定会找到EJB对象的home接口并返回。
return (jndiContext.lookup(jndiName));
}
由于Action类将是由Struts来调用的。所以它的主要方法应该是一个抽象方法,而由每个继承的子类来具体实现,或者在其中做一些所有Action都会做的通用机制,例如记录log信息。在本例中,我们一切从简,将其抽象之。
参数mapping:其类型为ActionMapping,将在本Action做跳转选择用
参数actionForm:由Struts根据本次HTTP请求数据填充完成的ActionForm对象(可选,如果存在请求数据的话)
参数request:此Action所有处理的本次HTTP请求(对象)
参数response:此Action输出数据所要用到的HTTP响应(对象)
如果有I/O错误出现,则本方法抛出IOException异常
如果处理时发生servlet异常,则本方法抛出ServletException异常
本方法处理完请求后按照处理逻辑返回相应的页面导向(对象)
public abstract ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
}
或者让这个抽象方法更有用一点,那就在里面干点什么吧,比如像下面这样在其中记录log。
{
ActionForward forward = null;
// 只是简单的记录一些提示信息到servlet log
getServlet().log(
"AbstStrutsActionBase.perform() [Action Class: "
+ this.getClass().getName()
+ " ]");
getServlet().log(
"AbstStrutsActionBase.perform() [Form Class : "
+ (form == null ? "null" : form.getClass().getName())
+ " ]");
}
然后,我们再编写的每个Action类都应该从AbstStrutsActionBase继承,并依照处理逻辑编写各自的perform方法。让我们用LoginAction为例,看看具体应该怎么应用吧。
package com.oreilly.actions;
import java.io.IOException;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionForward;
import com.oreilly.forms.LoginForm;
/*
LoginAction 将演示一个Action将如何被Struts架构所调用
在这个例子中,我们只是简单的演示perform方法是如何调用、执行并返回的
*/
public class LoginAction extends AbstStrutsActionBase {
接下来这个是验证用户的方法,本例中没有具体实现。但一个典型的应用方案是调用JavaBean或者EJB来完成。用来查找EJB的lookup方法(在基类中完成的)应该在本方法中被调用,其返回一个依据后台数据库验证用户的接口。
参数类型String,要验证的用户名
参数类型String,密码
返回类型boolean,如果验证通过为true,否则为false
public boolean authenticate(String username, String password) {
/* 本方法将先做一个查找动作,获得验证用户的EJB对象的接口并调用
* 由于本例只演示Action与商务逻辑层是如何交互的
* 所以具体实现代码本例中就不提供了:)
*/
return (true);
}
接下来我们在LoginAction中重载基类的perform方法。
参数mapping:其类型为ActionMapping,将在本Action做跳转选择用
参数actionForm:由Struts根据本次HTTP请求数据填充完成的ActionForm对象(可选,如果存在请求数据的话)
参数request:此Action所有处理的本次HTTP请求(对象)
参数response:此Action输出数据所要用到的HTTP响应(对象)
如果有I/O错误出现,则本方法抛出IOException异常
如果处理时发生servlet异常,则本方法抛出ServletException异常
本方法处理完请求后按照处理逻辑返回相应的页面导向(对象)
public ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// 先假定验证失败,那么要导向的forward当然是LOGIN了(见基类定义的全局变量)
boolean validLogin = false;
ActionForward actionForward = mapping.findForward(LOGIN);
// 构造出承载ActionError对象的容器——errors,以备错误出现时可用
ActionErrors errors = new ActionErrors();
// 从由本次请求构造的ActionForm中提取出所需要的数据
LoginForm loginForm = (LoginForm)form;
String userName = null;
String password = null;
if (loginForm != null) {
userName = loginForm.getUserName();
password = loginForm.getPassword();
validLogin = authenticate(userName, password);
}
if (validLogin) {
// 验证成功了,导向到struts-config.xml中定义的SUCCESS
actionForward = mapping.findForward(SUCCESS);
// 存点必要的东东到session,以备后用
request.getSession(true).setAttribute("USERNAME", userName);
} else {
errors.add("login", new ActionError("error.login.authenticate"));
}
// 系统如果用户界面友好一点,我们就应该将错误信息存入request对象中
// 然后到页面,通过在Struts的标签显示出来
if (!errors.empty()) {
saveErrors(request, errors);
}
// 本Action处理完成,导向到合适的forward
return (actionForward);
}
}
注意,这个LoginAction类就是在struts-config.xml中定义的用来处理登录事务的一个具体实现。当这个类被载入并有一个对象实例化后,Struts架构就会调用它的perform方法。这个方法是这样声明的:
public ActionForward perform(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
其中,mapping是一个记录与此Action对应的forward导向的对象,form对象封装由客户端提交的此Action要处理的数据,还有标准的HttpServletRequest对象和HttpServletResponse对象。
有了这些对象的辅助,此Action就可以拿到需要的东东顺利开工了。我们的例子中,要处理的数据主要是用户名和密码,这些都由form对象提供。实现验证功能是本应用的主要业务逻辑,在方法中的具体实现应该是去取EJB的相应接口来操作或者直接去拿数据库数据来验证。前面那个AbstStrutsActionBase类中已经实现了一个简单的EJB接口查找动作,所以如果我们是在开发一个基于EJB实现的系统,它的可重用性就非常强了。
由验证方法(authenticate())的返回值,Action要接着做出合理的动作。如果验证通过,就要让用户进入正确的页面,那么我们就将一些后面可能会用到的信息存入request对象(译注:准确的讲,代码中是存到了session对象里,当然session对象是和当前request相关的),并向Struts返回success这个forward。这个forward是在struts-config.xml中定义的,然后由ActionMapping封装起来,在Action处理中可以从中拿出合适的forward做为返回值。如果回头去看看struts-config.xml中的定义,就会知道success这个forward会将用户导向至Welcome.jsp这个页面的。如果验证失败,则将一个错误信息存起来,然后导向到一个错误提示页面显示出来。
开发应用的业务逻辑
在一个真实的应用系统中,我们应该将业务逻辑层整合进来了。在我们这个例子里,我们就应该去开发LoginAction中的authenticae方法所调用到的EJB了。但是正如你所见的,我们完全可以把这一层暂时屏蔽掉,而利用Struts把前端部分构建并能够让它跑起来的。我其实相当推崇的是方法是先将应用框架搭建并运行起来,然后在开发后台实际的业务逻辑层。在应用框架完全恰当的构建起来的时候,后台的开发工作所有做的debug工作也少的多了。而且,业务逻辑的开发也不是本文所要函概的范围,所以此处我们略过,不过我相信你现在一定对应用的全局有了总体的把握了吧!
开发由ActionMapping定义的系统工作流程,完成对应的JSP页面
终于可以将所有这些东东整合在一起了。在struts-config.xml配置文件中定义的那些ActionMapping,我们要完成这些ActionMapping定义用到的JSP页面。本例中,包括Login.jsp、Welcome.jsp和Errorpage.jsp。还有,尽管我们在本例中都是将Action处理完成forward到JSP页面,这在这个简单的例子中是再恰当不过的逻辑流程了,而在实际利用Struts开发应用中呢,当然可以从Action forward到其他的Action。我们这个简单的Login.jsp页面内容是这样的:
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-form.tld" prefix="form" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<html>
<head>
<title><bean:message key="login.title"/></title>
</head>
<body>
<html:errors/>
<h3>Enter your username and password to login:</h3>
<html:form action="login.action" focus="userName" >
<html:text property="userName" size="30" maxlength="30"/>
<html:password property="password" size="16" maxlength="16" redisplay="false"/>
<html:submit property="submit" value="Submit"/>
<html:reset/>
</html:form>
</body>
</html>
Struts在JSP自定义标签库的基础上提供了一套综合各种功能的标签库。利用这些标签库很容易的构建用户界面了。使用这些标签库的好处之一就是可以利用其提供的很多附加功能。比如在一般的JSP页面的表单里我们可以看到这样常见的HTML片断:
<input type="text" name="userName" value="">
如果我们使用Struts的标签库的话,就可以改成这样子:
<html:text property ="userName">
不过我们得现在页面中先声明Struts标签库的定义。
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
在这个例子中,我们会用到一些Struts标签,但我不准备在此详细讲解Struts各种标签库的用法。相信在你不断使用Struts搭建功能复杂的JSP页面的过程中,你将会对所使用过的标签越来越熟悉的。到那时,你也将更能体会到Struts标签的益处,利用它们大大的缩短你的开发时间。目前,你可以从Struts Developers Guides了解到更多的细节。
在我们这个简单例子中,有两个重点。其一:
<title><bean:message key="login.title"/></title>
这就是在利用我们前面提到的资源文件ApplicationResource来在页面显示信息,而不是将信息文本硬编码到我们的应用中。
其二:
<html:errors/>
这就是在页面中显示出ActionErrors的信息,也就是我们在LoginForm的验证方法和LoginAction中产生的报错信息的集合对象。
页面中的表单,利用Struts,我们将用如下的标签来定义:
<html:form action="login.action" focus="userName">
这里的login.action,是和struts-config.xml中定义ActionMapping相匹配的。在页面标签中这样的定义,就将相关的Action、ActionForm和ActionForward完整的串了起来。当这个用标签定义的表单提交的时候,Struts中的ActionServlet就会将其交由login.action来处理。具体的过程我们下面慢慢深入。
在Welcome.jsp中,我们只演示如何将Action中的信息传递到页面加以利用的一般机制:
<html>
<title>Welcome to Struts</title>
<body>
<p>Welcome <%= (String)request.getSession().getAttribute("USERNAME") %></p>
</p>You have logged in successfully!</p>
</body>
</html>
还记得吗?我们在LoginAction中的perform()方法中将USERNAME放到了session中哦。
完成系统配置文件
我们已经就struts-config.xml谈了好多了。通常,这个文件中的信息会在开发过程中逐渐完善。但是到了开发过程的最后一部,我们更应该回头去检查这个至关重要的配置文件,以保证万无一失:Action、JSP页面还有ActionForm都应该在文件中正确的定义。此外,我们还不得不说到web.xml。这个文件是JSP容器(例如Tomcat)获取应用相关配置的重要文件。我们这个StrutsSample例子所用到的web.xml大致是这样的:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
This is the web-app configuration that allow the strutsSample to work under
Apache Tomcat.
-->
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"
http://java.sun.com/j2ee/dtds/web-app_2_2.dtd";>
<web-app>
<servlet>
<servlet-name>oreilly</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>application</param-name>
<param-value>com.oreilly.ApplicationResources</param-value>
</init-param>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>validate</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>oreilly</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>Login.jsp</welcome-file>
</welcome-file-list>
<!-- Struts Tag Library Descriptors -->
<taglib>
<taglib-uri>/WEB-INF/struts.tld</taglib-uri>
<taglib-location>/WEB-INF/struts.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-form.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-form.tld</taglib-location>
</taglib>
</web-app>
这里的<servlet>标签定义了org.apache.struts.action.ActionServlet,而且在本例中,我们把这个定义的servlet叫作“oreilly”,并传了两个初始化参数给它:其一是我们为这个应用所需的显示字符串定义的资源文件,其二是指明struts-config.xml文件的位置。相信你也注意到了,在<servlet-mapping>中为这个Servlet指明的相应请求处理串是*.action,这是和我们在页面中的表单定义的提交的URL是吻合的。也就是说,我们通过<servlet-mapping>标签告诉Tomcat,所有后缀为.action的请求都交给“oreilly”这个Servlet来处理。你当然可以指定你喜欢的后缀。在Struts附带的例子中,你可能会看到通常以.do做为后缀,不过我认为.action更明确一些。<welcome-file-list>标签中定义了本应用初始显示页面。最后呢,我们还要把会用到的Struts标签库列在后面。
编译/测试/发布
到此为止,编译/测试/发布应用之前的所有工作都完成了。用Ant来编译整个应用是相当容易的。如果你以前没有接触过Ant,那最好把这个研究一下。其实学习和应用Ant来管理一个应用编译环境并不难。我把这个编译应用所要用到的build.xml和例子放到了一起,这篇文章所要用到的所有东东,你都可以点此下载,到时候你到build.xml所在目录简单执行ant命令就可以完成编译,并打包成strutsSample.war包。当然要执行ant,你得先去下载Ant。将Ant下载回来并搭建好环境可能得花十几分钟的时间哦。
本应用的目录结构如下:
StrutsSample根目录
*.jsp
WEB-INF目录
Struts配置文件(struts-config.xml, web.xml)
classes目录(还是以Java程序文件包结构为路径)
lib目录(struts.jar)
拿到了应用的war包,我们就将它放到Tomcat的webapps路径下,然后启动Tomcat。war包会被自动展开,此应用的上下文环境也会由Tomcat自动建立起来。我们通过web.xml告知Tomcat这个应用所需的其他资源在哪里。现在,我们可以通过
http://localhost:8080/strutsSample来访问我们的应用了。如果没有特别指定的话,Tomcat默认的端口是8080,我们定义的默认初始页面Login.jsp也将显示出来,现在我们来试试吧。
结论
通过本系列的文章,我们利用Struts从应用需求开始,一步步将整个应用搭建起来。和普通的JSP技术相比,通过Struts开发的应用涉及到更多的与之相关的各类文件,也正是依靠各类文件,我们才可能构建一个适合开发复杂应用的MVC架构。我们的第一个Struts应用花了如此多的时间,是为了要弄清楚Struts的各个部分到底是如何工作的。
希望本系列Struts文章,能够帮助你了解Struts是由哪些部分构成的,它们能够完成什么,也希望介绍一个比较好的开发流程可供你参考。Struts才诞生不久,我有信心它将成为我们构建J2EE应用的优秀工具。