??xml version="1.0" encoding="utf-8" standalone="yes"?> 说明Q?/B>希望本文的读者能有一定的Struts使用基础? Struts是基于Model 2之上的,而Model 2是经典的MVCQ模型-视图Q控制器Q模型的Web应用变体Q这个改变主要是׃|络应用的特?-HTTP协议的无状态性引L。Model 2的目的和MVC一P也是利用控制器来分离模型和视图,辑ֈ一U层间松散耦合的效果,提高pȝ灉|性、复用性和可维护性。在多数情况下,你可以将Model 2与MVC{同h?/P>
下图表示一个基于Java技术的典型|络应用Q从中可以看出Model 2中的各个部分是如何对应于Java中各U现有技术的?/P>
在利用Model 2之前Q我们是把所有的表示逻辑和业务逻辑都集中在一P比如大杂烩似的JSPQ,有时也称q种应用模式为Model 1QModel 1的主要缺点就是紧耦合Q复用性差以及l护成本高?/P>
既然Struts 1.1是基于Model 2之上Q那它的底层机制也就是MVCQ下面是Struts 1.1中的MVC实现C意图: 首先Q控制器QActionServletQ进行初始化工作Q读取配|文Ӟstruts-config.xmlQ,Z同的Struts模块初始化相应的ModuleConfig对象。比如配|文件中的Action映射定义都保存在ActionConfig集合中。相应地有ControlConfig集合、FormBeanConfig集合、ForwardConfig集合和MessageResourcesConfig集合{?/P>
提示Q?/B>模块是在Struts 1.1中新提出的概念,在稍后的内容中我们将详细介绍Q你现在可以单地把模块看作是一个子pȝQ它们共同组成整个应用,同时又各自独立。Struts 1.1中所有的处理都是在特定模块环境中q行的。模块的提出主要是ؓ了解决Struts 1.0中单配置文g的问题? 控制器接收HTTPhQƈ从ActionConfig中找出对应于该请求的Action子类Q如果没有对应的ActionQ控制器直接请求{发给JSP或者静态页面。否则控制器请求分发至具体Actionc进行处理?/P>
在控制器调用具体Action的executeҎ(gu)之前QActionForm对象利用HTTPh中的参数来填充自己(可选步骤,需要在配置文g中指定)。具体的ActionForm对象应该是ActionForm的子cd象,它其实就是一个JavaBean。此外,q可以在ActionFormcM调用validateҎ(gu)来检查请求参数的合法性,q且可以q回一个包含所有错误信息的ActionErrors对象。如果执行成功,ActionForm自动这些参C息以JavaBeanQ一般称之ؓform beanQ的方式保存在Servlet Context中,q样它们可以被其它Action对象或者JSP调用?/P>
Struts这些ActionForm的配|信息都攑֜FormBeanConfig集合中,通过它们Struts能够知道针对某个客户h是否需要创建相应的ActionForm实例?/P>
Action很简单,一般只包含一个executeҎ(gu)Q它负责执行相应的业务逻辑Q如果需要,它也q行相应的数据检查。执行完成之后,q回一个ActionForward对象Q控制器通过该ActionForward对象来进行{发工作。我们主张将获取数据和执行业务逻辑的功能放到具体的JavaBean当中Q而Action只负责完成与控制有关的功能。遵循该原则Q所以在上图中我Action对象归ؓ控制器部分?/P>
提示Q?/B>其实在Struts 1.1中,ActionMapping的作用完全可以由ActionConfig来替代,只不q由于它是公共API的一部分以及兼容性的问题得以保留。ActionMapping通过l承ActionConfig来获得与其一致的功能Q你可以{同地看待它们。同理,其它例如ActionForward与ForwardConfig的关pM是如此? 下图l出了客L从发求到获得响应整个q程的图解说明?/P>
下面我们来详细地讨Z下其中的每个部分Q在q之前,先来了解一下模块的概念?/P>
我们知道Q在Struts 1.0中,我们只能在web.xml中ؓActionServlet指定一个配|文Ӟq对于我们这些网上的教学例子来说当然没什么问题,但是在实际的应用开发过E中Q可能会有些ȝ。因多开发h员都可能同时需要修攚w|文Ӟ但是配置文g只能同时被一个h修改Q这栯定会造成一定程度上的资源争夺,势必会媄响开发效率和引v开发h员的抱怨?/P>
在Struts 1.1中,Z解决q个q行开发的问题Q提Z两种解决Ҏ(gu)Q?
支持多个配置文gQ是指你能够为ActionServlet同时指定多个xml配置文gQ文件之间以逗号分隔Q比如Struts提供的MailReader演示例子中就采用该种Ҏ(gu)?/P>
q种Ҏ(gu)可以很好地解决修改冲H的问题Q不同的开发h员可以在不同的配|文件中讄自己的Action、ActionForm{等Q当然不是说每个开发h员都需要自q配置文gQ可以按照系l的功能模块q行划分Q。但是,q里q是存在一个潜在的问题Q就是可能不同的配置文g之间会生冲H,因ؓ在ActionServlet初始化的时候这几个文g最l还是需要合q到一L。比如,在struts-config.xml中配|了一个名为success?lt;forward>Q而在struts-config-registration.xml中也配置了一个同L<forward>Q那么执行v来就会生冲H?/P>
Zd解决q种冲突QStruts 1.1中引q了模块QModuleQ的概念。一个模块就是一个独立的子系l,你可以在其中q行L所需的配|,同时又不必担心和其它的配|文件生冲H。因为前面我们讲q,ActionServlet是将不同的模块信息保存在不同的ModuleConfig对象中的。要使用模块的功能,需要进行以下的准备工作Q?/P>
1、ؓ每个模块准备一个配|文?/P>
2、配|web.xml文gQ通知控制?/P>
军_采用多个模块以后Q你需要将q些信息告诉控制器,q需要在web.xml文gq行配置。下面是一个典型的多模块配|: 要配|多个模块,你需要在原有的一?lt;init-param>Q在Struts 1.1中将其对应的模块UCؓ~省模块Q的基础之上Q增加模块对应的<init-param>。其?lt;param-name>表示为config/XXX的Ş式,其中XXX为对应的模块名,<param-value>中还是指定模块对应的配置文g。上面这个例子说明该应用有三个模块,分别是缺省模块、customer和orderQ它们分别对应不同的配置文g?/P>
3、准备各个模块所需的ActionForm、Action和JSP{资?/P>
但是要注意的是,模块的出C同时带来了一个问题,卛_何在不同模块间进行{发?有两U方法可以实现模块间的{发,一U就是在<forward>Q全局或者本圎ͼ中定义,另外一U就是利用org.apache.struts.actions.SwitchAction?/P>
下面是一个全局的例子: 可以看出Q只需要在原有的path属性前加上模块名,同时contextRelative属性置为true卛_。此外,你也可以?lt;action>中定义一个类似的本地<forward>?/P>
如果你已l处在其他模块,需要{回到~省模块Q那应该cM下面q样定义Q即模块名ؓI?/P>
此外Q你也可以用org.apache.struts.actions.SwitchActionQ例如: 我们首先来了解MVC中的控制器。在Struts 1.1中缺省采用ActionServletcL充当控制器。当然如果ActionServlet不能满你的需求,你也可以通过l承它来实现自己的类。这可以?WEB-INF/web.xml中来具体指定?/P>
要掌握ActionServletQ就必须了解它所扮演的角艌Ӏ首先,ActionServlet表示MVCl构中的控制器部分,它需要完成控制器所需的前端控制及转发h{职责。其ơ,ActionServlet被实Cؓ一个专门处理HTTPh的ServletQ它同时hservlet的特炏V在Struts 1.1中它主要完成以下功能Q?
此外QActionServletq负责初始化和清除应用配|信息的d。ActionServlet的初始化工作在initҎ(gu)中完成,它可以分Z个部分:初始化ActionServlet自n的一些信息以及每个模块的配置信息。前者主要通过initInternal、initOther和initServlet三个Ҏ(gu)来完成?/P>
我们可以?WEB-INF/web.xml中指定具体的控制器以及初始参敎ͼ׃版本的变化以及Struts 1.1中模块概늚引进Q一些初始参数被废弃或者移入到/WEB-INF/struts-config.xml中定义。下面列出所有被废弃的参敎ͼ相应地在web.xml文g中也不鼓励再使用?/P>
ActionServletҎ(gu)不同的模块来初始化ModuleConfigc,q在其中以XXXconfig集合的方式保存该模块的各U配|信息,比如ActionConfigQFormBeanConfig{?/P>
初始化工作完成之后,ActionServlet准备接收客户h。针Ҏ(gu)个请求,Ҏ(gu)process(HttpServletRequest request, HttpServletResponse response)被调用。该Ҏ(gu)指定具体的模块,然后调用该模块的RequestProcessor的processҎ(gu)?/P>
RequestProcessor包含了Struts控制器的所有处理逻辑Q它调用不同的processXXXҎ(gu)来完成不同的处理。下表列出其中几个主要的Ҏ(gu)Q?/P>
<!-- Action Servlet Configuration -->
<servlet>
<servlet-name>action</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, /WEB-INF/struts-config-registration.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>config/customer</param-name>
<param-value>/WEB-INF/struts-config-customer.xml</param-value>
</init-param>
<init-param>
<param-name>config/order</param-name>
<param-value>/WEB-INF/struts-config-order.xml</param-value>
</init-param>
...
<struts-config>
...
<global-forwards>
<forward name="toModuleB"
contextRelative="true"
path="/moduleB/index.do"
redirect="true"/>
...
</global-forwards>
...
</struts-config>
<action-mappings>
<!-- Action mapping for profile form -->
<action path="/login"
type="com.ncu.test.LoginAction"
name="loginForm"
scope="request"
input="tile.userLogin"
validate="true">
<forward name="success" contextRelative="true" path="/moduleA/login.do"/>
</action>
</action-mappings>
<forward name="success" contextRelative="true" path="/login.do"/>
...
<action-mappings>
<action path="/toModule"
type="org.apache.struts.actions.SwitchAction"/>
...
</action-mappings>
...
protected void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
RequestUtils.selectModule(request, getServletContext());
getRequestProcessor(getModuleConfig(request)).process(request, response);
}
Ҏ(gu)
功能
processPath
获取客户端的h路径
processMapping
利用路径来获得相应的ActionMapping
processActionForm
初始化ActionFormQ如果需要)q存入正的scope?/TD>
processActionCreate
初始化Action
processActionPerform
调用Action的executeҎ(gu)
processForwardConfig
处理Actionq回的ActionForward
对于ActionForm你可以从以下几个斚w来理解它Q?
ActionForm首先利用属性的getter和setterҎ(gu)来实现初始化Q初始化完毕后,ActionForm的validateҎ(gu)被调用,你可以在其中来检查请求参数的正确性和有效性,q且可以错误信息以ActionErrors的Ş式返回到输入H体。否则,ActionForm被作ؓ参数传给action的executeҎ(gu)以供使用?/P>
ActionForm bean的生命周期可以设|ؓsessionQ缺省)和requestQ当讄为sessionӞ记得在resetҎ(gu)中将所有的属性重新设|ؓ初始倹{?/P>
׃ActionForm对应于HTTPH体Q所以随着面的增多,你的ActionForm会急速增加。而且可能同一cd面字段会在不同的ActionForm中出玎ͼq且在每个ActionForm中都存在相同的验证代码。ؓ了解册个问题,你可以ؓ整个应用实现一个ActionForm或者至一个模块对应于一个ActionForm?/P>
但是Q聚合的代h(hun)是复用性很差,而且隄护。针对这个问题,在Struts 1.1中提ZDynaActionForm的概c?/P>
DynaActionFormc?/B>
DynaActionForm的目的就是减ActionForm的数目,利用它你不必创徏一个个具体的ActionFormc,而是在配|文件中配置出所需的虚拟ActionForm。例如,在下表中通过指定<form-bean>的type?org.apache.struts.action.DynaActionForm"来创Z个动态的ActionForm--loginForm?/P>
|
动态的ActionForm的用方法跟普通的ActionForm相同Q但是要注意一炏V普通的ActionForm对象需要ؓ每个属性提供getter和setterҎ(gu)Q以上面的例子而言Q我们需要提供getUsername() ?setUsername()Ҏ(gu)取得和设|username属性,同样地有一Ҏ(gu)法用于取得和讄password属性和actionClass属性?/P>
如果使用DynaActionFormQ它?yu)属性保存在一个HashMapcd象中Q同时提供相应的get(name) ?set(name)Ҏ(gu)Q其中参数name是要讉K的属性名。例如要讉KDynaActionForm中username的|可以采用cM的代码:
|
׃值存放于一个HashMap对象Q所以要记得对get()Ҏ(gu)q回的Object对象做强制性类型{换。正是由于这点区别,如果你在Action中非帔RJ地使用ActionForm对象Q徏议还是用普通的ActionForm对象?/P>
在Struts 1.1中,除了DynaActionForm以外Q还提供了表单输入自动验证的功能Q在包org.apache.struts.validator中提供了许多有用的类Q其中最常见的就是DynaValidatorFormcR?/P>
DynaValidatorFormc?/B>
DynaValidatorForm是DynaActionForm的子c,它能够提供动态ActionForm和自动表单输入验证的功能。和使用DynaActionFormcMQ你必须首先在配|文件中q行配置Q?/P>
|
同时要定义验证的插gQ?/P>
|
其中的validator.xml和validator-rules.xml分别表示验证定义和验证规则的内容Q可以合q在一PQ比如针对上例中的DynaValidatorFormQ我们有如下验证定义Qvalidator.xmlQ:
|
从上q定义中Q我们可以看到对于字Dusername有三w证:required, minlength, maxlengthQ意思是该字D不能ؓI,而且长度??6之间。而validator-rules.xml文g则可以采用Struts提供的缺省文件。注意在<form-bean>中定义的form是如何与validation.xml中的form兌h的。最后,要启动自动验证功能,q需要将Action配置的validate属性设|ؓtrue?/P>
|
此时QStruts根据xml配置文g中的定义来检验表单输入,q将不符合要求的错误信息输出到页面。但是你可能会想Q这个功能虽然好Q可是什么检验都跑到服务器端执行Q效率方面和用户易用性方面是不是有些问题Q你可能会怀念v那简单的JavaScript客户端验证?/P>
不用担心Q在Struts 1.1中也支持JavaScript客户端验证。如果你选择了客L验证Q当某个表单被提交以后,Struts 1.1启动客户端验证,如果览器不支持JavaScript验证Q则服务器端验证被启动,q种双重验证机制能够最大限度地满各种开发者的需要。JavaScript验证代码也是在validator-rules.xml文g中定义的。要启动客户端验证,你必d相应的JSP文g中做如下讄Q?
下表中列Z一JSP文g的示例代码,U字部分为Javascript验证所需代码?/P>
|
其中onsubmit的gؓ"return validateLoginForm(this);"Q它的语法ؓQ?/P>
return validate + struts-config.xml中定义的form-bean名称 + (this);
staticJavascript.jsp的内容ؓQ?/P>
|
如果validator-rules.xml中定义的基本验证功能不能满你的需求,你可以自己添加所需的验证类型?/P>
我们通过l承ActioncL实现具体的执行类。具体Actioncȝ功能一般都在executeQ以前是performҎ(gu)Q方法中完成Q其中主要涉及到以下几个斚wQ?
提示Q?/B>׃在Action和ActionForm中都可以实现验证Ҏ(gu)Q那么如何来安排它们之间的分工呢Q一般来_我们U着MVC分离的原则,也就是视囄的验证工作放在ActionForm来完成,比如输入不能为空Qemail格式是否正确Q利用ValidatorForm可以很轻村֜完成q些工作。而与具体业务相关的验证则攑օAction中,q样可以获得最大ActionForm重用性的可能?
前面我们提到q,我们d业务逻辑执行分离到单独的JavaBean中,而Action只负责错误处理和程控制。而且考虑到重用性的原因Q在执行业务逻辑的JavaBean中不要引用Q何与Web应用相关的对象,比如HttpServletRequestQHttpServletResponse{对象,而应该将其{化ؓ普通的Java对象。关于这一点,可以参考Petstore中WAF框架的实现思\?/P>
此外Q你可能q注意到execute与perform的一个区别:executeҎ(gu)单地掷出Exception异常Q而performҎ(gu)则掷出ServletException和IOException异常。这不是说Struts 1.1在异常处理功能方面弱化了Q而是Z配合Struts 1.1中一个很好的功能--宣称式异常处理机制?/P>
和EJB中的宣称式事务处理概늱|宣称式异常处理其实就是可配置的异常处理,你可以在配置文g中指定由谁来处理ActioncM掷出的某U异常。你可以按照以下步骤来完成该功能Q?
下表定义了一个全局的处理类CustomizedExceptionHandlerQ它被用来处理所有的异常?/P>
|
其中具体的参数含义,可以参考ExceptionHandler.java源文件?/P>
讲完了模型和控制器,接下来我们要涉及的是视图。视囄角色主要是由JSP来完成,从JSP的规范中可以看出Q在视图层可?折腾"的技术不是很多,主要的就是自定义标记库的应用。Struts 1.1在原有的四个标记库的基础上新增了两个标记?-Tiles和Nested?/P>
其中Tiles除了替代Template的基本模板功能外Q还增加了布局定义、虚拟页面定义和动态页面生成等功能。Tiles强大的模板功能能够ə面获得最大的重用性和灉|性,此外可以l合Tiles配置文g中的面定义和Action的{发逻辑Q即你可以将一个Action转发C个在Tiles配置文g中定义的虚拟面Q从而减页面的数量。比如,下表中的Action定义了一个{发\径,它的l点是tile.userMainQ而后者是你在Tiles配置文g中定义的一个页面?/P>
|
Tiles配置文gQtiles-defs.xml
|
而Nested标记库的作用是让以上q些基本标记库能够嵌套用,发挥更大的作用?/P>
所谓的Commons Logging接口Q是指将日志功能的用与日志具体实现分开Q通过配置文g来指定具体用的日志实现。这样你可以在Struts 1.1中通过l一的接口来使用日志功能Q而不ȝ具体是利用的哪种日志实现Q有点于cMJDBC的功能。Struts 1.1中支持的日志实现包括QLog4JQJDK Logging APIQ?LogKitQNoOpLog和SimpleLog?/P>
你可以按照如下的方式来用Commons Logging接口Q可以参照Struts源文中的许多cd玎ͼQ?/P>
|
而开启日志功能最单的办法是在WEB-INF/classes目录下添加以下两个文Ӟ
commons-logging.properties文gQ?/P>
|
simplelog.properties文gQ?/P>
|
q里我们采用的日志实现是SimpleLogQ你可以在simplelog.properties文g指定日志明细的别:traceQdebugQinfoQwarnQerror和fatalQ从trace到fatal错误U别来高Q同时输出的日志信息也越来越。而这些别是和org.apache.commons.logging.log接口中的Ҏ(gu)一一对应的。这些别是向后包含的,也就是前面的U别包含后面U别的信息?/P>
关于作?/SPAN> 王和?邮g地址Q?ok_winnerboy@sina.com 原文章地址Q?BR>http://www-128.ibm.com/developerworks/cn/java/l-struts1-1/ |