Struts 2.2.3.1指南:核心开发指南(第4部分:配置元素)

1.3.3 配置元素
Web应用程序使用部署描述符来初始化资源,比如Servlet和taglib。部署描述符的格式为XML文档,且名称为web.xml。

同样,框架也使用了一个配置文件来初始化它自己的资源,这些资源包括:
(1)可以对请求进行预处理或后处理的拦截器;
(2)可以调用业务逻辑或访问数据的Action类;
(3)可以准备视图的Result,例如:JSP和FreeMarker模板。

在运行时,一个应用程序只有一个配置。在运行时之前,通过一个或多个XML文档来定义配置,包括默认的struts.xml文档。可以配置很多元素,包括:packages、namespaces、includes、actions、results、interceptors、exceptions。操作示例,请查看struts.xml。

管理元素
(1)Bean配置
(2)Constant配置
(3)Package配置
(4)Namespace配置
(5)Include配置
请求处理元素
(1)Interceptor配置
(2)Action配置
(2.1)Wildcard映射
(3)Result配置
(4)Unknown处理器
错误处理
(1)Exception配置
1.3.3.1 配置Bean
在内部,框架使用它自己的依赖注入容器。这个容器会加载关键的框架对象,因此,框架的任何一部分都可以以一种标准的,一致的方式来替换、扩展、或移除。特别是插件,通过这个功能来扩展框架,从而提供对第三方库的支持,例如Spring和Sitemesh。大多数的应用程序都不需要扩展Bean的配置。
1.3.3.1.1 bean
bean元素有一个必要的class属性,这个属性用于指定需要被创建或操作的Java类。一个bean可以:(1)由框架的容器创建,并注入到内部框架对象;(2)包含被注入到它的静态方法的值。

第一种用法,对象注入,通常伴随着type属性,用于告诉容器这个对象实现了哪个接口。
第二种用法,值注入,好处在于允许那些不是由容器创建的对象接收框架的常量。使用值注入的对象必须定义静态属性。

属性    必要    描述
class    必要    bean的类名
type    可选    这个类实现的主要Java接口
name    可选    bean的唯一名称,必须在指定了相同type的其它bean之间是唯一的。
scope    可选    bean的范围,可取值为:default、singleton、request、session、thread
static    可选    是否注入静态方法,指定了type时,不能为true。
optional    可选    bean是否是可选的

示例用法
Bean Example (struts.xml)
<struts>
    <bean type="com.opensymphony.xwork2.ObjectFactory" name="myfactory"
        class="com.company.myapp.MyObjectFactory" />
    ...
</struts>
1.3.3.2 配置常量
常量提供了一个简单的方式,通过定义修改框架和插件行为的Key设置来定制Struts应用程序。常量有两种关键角色:(1)用于覆盖一些设置,例如,文件上传的最大大小,或者Struts框架是否是devMode模式,等等;(2)在一个给定类型的多个实现中,选择指定哪个Bean实现。

常量可以在多个文件中声明,默认情况下,按照以下顺序搜索常量,后续的文件可以覆盖前面的文件:
(1)struts-default.xml
(2)struts-plugin.xml
(3)struts.xml
(4)struts.properties
(5)web.xml
提供struts.properties文件是为了向后兼容WebWork。
1.3.3.2.1 constant
在各种XML文件中,constant元素都包含两个必要的属性:name和value:
属性    必要    描述
name    必要    常量名称
value    必要    常量值

在struts.properties文件中,每个条目被看做一个常量。
在web.xml文件中,FilterDispatcher的初始化参数被作为常量加载。

示例用法
Constant Example (struts.xml)
<struts>
    <constant name="struts.devMode" value="true" />
    ...
</struts>

Constant Example (struts.properties)
struts.devMode = true

Constant Example (web.xml)
<web-app id="WebApp_9" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <filter>
        <filter-name>struts</filter-name>
        <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
        <init-param>
            <param-name>struts.devMode</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    ...
</web-app>
1.3.3.3 配置包
包是一种用于把action、result、result类型、interceptor、interceptor-stack组织为一个逻辑配置单元的方式。概念上,包类似于对象,它们可以被扩展,并且个别部分可以被子包覆盖。
1.3.3.3.1 package
package元素有一个必要的name属性,这个属性作为后续引用这个包的key。extends属性是可选的,它允许一个包继承一个或多个之前的包的配置,包括所有的interceptor、interceptor-stack、action的配置。

注意,配置文件是从上往下处理的,因此extends应用的包应该在这个包之前定义。
可选的abstract属性用于创建一个可以省略action配置的父包。

属性    必要    描述
name    必要    其它包引用的key
extends    可选    继承这个包扩展的包的行为
namespace    可选    查看配置Namespace
abstract    可选    声明一个抽象包,不需要action配置

示例用法
Package Example (struts.xml)
<struts>
    <package name="employee" extends="struts-default" namespace="/employee">
        <default-interceptor-ref name="crudStack"/>

        <action name="list" method="list"
                class="org.apache.struts2.showcase.action.EmployeeAction" >
            <result>/empmanager/listEmployees.jsp</result>
            <interceptor-ref name="basicStack"/>
        </action>
        <action name="edit-*" class="org.apache.struts2.showcase.action.EmployeeAction">
            <param name="empId">{1}</param>
            <result>/empmanager/editEmployee.jsp</result>
            <interceptor-ref name="crudStack">
                <param name="validation.excludeMethods">execute</param>
            </interceptor-ref>
        </action>
        <action name="save" method="save"
                class="org.apache.struts2.showcase.action.EmployeeAction" >
            <result name="input">/empmanager/editEmployee.jsp</result>
            <result type="redirect">edit-${currentEmployee.empId}.action</result>
        </action>
        <action name="delete" method="delete"
                class="org.apache.struts2.showcase.action.EmployeeAction" >
            <result name="error">/empmanager/editEmployee.jsp</result>
            <result type="redirect">edit-${currentEmployee.empId}.action</result>
        </action>
    </package>
</struts>
1.3.3.4 配置命名空间
命名空间(namespace)属性把action配置细分为逻辑模块,每个action都有其自己的标识符前缀。命名空间避免了action名称之间的冲突。每个命名空间都可以有自己的menu或help action,每个action都有其自己的实现。虽然前缀会出现的浏览器的URI中,但是标签是可以感知命名空间的,因此命名空间前缀不需要嵌入到表单和链接中。

Struts 2的命名空间等同于Struts 1的Action模块,但是它更加方便灵活。
1.3.3.4.1 默认的命名空间
默认的命名空间为“”,一个空字符串。默认的命名空间是一个“一网打尽”的命名空间。如果在指定的命名空间中没有找到action的配置,默认的命名空间也会被搜索。局部/全局策略允许一个应用程序在action元素的extends层次结构之外拥有全局的action配置。

命名空间前缀可以与Java声明性安全一起注册,确保只有授权用户可以访问给定命名空间中的action。
1.3.3.4.2 根命名空间
同样也支持根命名空间“/”,根命名空间是接收的请求直接在上下文路径下时的命名空间。与其它命名空间一样,如果没有找到局部的action,就会回到默认的命名空间中查找。
1.3.3.4.3 命名空间示例
<package name="default">
    <action name="foo" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">greeting.jsp</result>
    </action>

    <action name="bar" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">bar1.jsp</result>
    </action>
</package>

<package name="mypackage1" namespace="/">
    <action name="moo" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">moo.jsp</result>
    </action>
</package>

<package name="mypackage2" namespace="/barspace">
    <action name="bar" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">bar2.jsp</result>
    </action>
</package>
1.3.3.4.4 代码是如何工作的
如果发出了一个对/barspace/bar.action的请求,那么就会在/barspace命名空间中搜索bar action。如果找到了,bar action就会被执行,否则,就会回到默认的命名空间。在命名空间示例中,bar action存在于/barspace命名空间,因此bar action将会被执行,如果返回了success,那么请求会被转发给bar2.jsp。

回到foo
如果发出了一个对/barspace/foo.action的请求,那么就会在/barspace命名空间中检查foo action。如果没有找到局部的action,那么将会检查默认的命名空间。在命名空间示例中,/barspace命名空间中没有foo action,因此会检查默认的命名空间,且/foo.action将会被执行。

在命名空间示例中,如果发出了一个对moo.action的请求,那么就会在根命名空间“/”中搜索moo action;如果在根命名空间中没有找到这个action,就会检查默认的命名空间。在这个例子中,moo action存在,并且会被执行。如果返回success,请求会被转发给bar2.jsp。

访问根命名空间
如果发出了一个对/foo.action的请求,那么就会检查根命名空间“/”。如果找到foo,那么就会选择根命名空间中的action。此外,框架还会检查默认的命名空间。在命名空间示例中,foo action不存在于根命名空间,因此会检查默认的命名空间,并且,会执行默认的命名空间中的foo action。

命名空间不是路径
命名空间不是像文件系统路径那样的层次结构,只有一个命名空间层级。例如,如果请求了URL  /barspace/myspace/bar.action,框架首先会查找/barspace/myspace命名空间。如果action在 /barspace/myspace中不存在,会立即回到默认的命名空间中查找。框架不会把命名空间解析为一系列文件夹。在命名空间示例中,将会选择在默认命名空间中的bar action。
1.3.3.5 配置Include
一种受欢迎的策略是分而治之,使用<include .../>元素,框架可以把分而治之的策略应用于配置文件。
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <include file="Home.xml"/>
    <include file="Hello.xml"/>
    <include file="Simple.xml"/>
    <include file="/util/POJO.xml"/>
    <include file="/com/initech/admin/admin-struts.xml"/>
</struts>

每个被include的文件都必须与struts.xml一样具有相同的格式,包括DOCTYPE。被include的文件可以放置在classpath中的任何地方,并且应该以路径的方式被file属性引用。

在一个大型的团队环境中,include文件可以用于组织应用程序的不同模块,这些模块由不同的团队成员开发。
1.3.3.6 配置拦截器
拦截器允许你定义在Action方法被执行之前或之后执行的代码。在开发应用程序时,(过滤器模式)拦截器可以成为一个强大的工具。有太多太多的拦截器用例,包括:验证、填充属性、安全、记录日志和性能分析。
验证    验证输入的正确性
填充属性    把输入转移并转换给对象的属性
日志    每个action的日志明细
性能分析    记录action的执行时间,查找性能瓶颈

可以把拦截器连接在一起创建一个拦截器栈。如果一个action需要验证客户证书,记录action日志,并记录action时间,所有这些例程,以及更多,可以组成相同拦截器栈的一部分。

拦截器是Java类实现的,因此每个拦截器都有一个类名。为了更容易地引用拦截器,每个拦截器类都可以在框架中注册,并给定一个唯一的,更简单的名称。
Registering Interceptors
<interceptors>
    <interceptor name="security" class="com.company.security.SecurityInterceptor"/>
    <interceptor-stack name="secureStack">
        <interceptor-ref name="security"/>
        <interceptor-ref name="defaultStack"/>
    </interceptor-stack>
</interceptors>

单个拦截器和拦截器栈可以在定义一个拦截器栈时以任何顺序混合搭配。框架会按照拦截器栈中拦截器的定义顺序来调用每个拦截器。

每个应用程序都会定义一个默认的拦截器栈,例如:
<default-interceptor-ref name="secureStack"/>

但是任何action也可以定义它自己的局部拦截器栈。
A local Interceptor Stack
<action name="VelocityCounter" class="org.apache.struts2.example.counter.SimpleCounter">
    <result name="success">...</result>
    <interceptor-ref name="defaultComponentStack"/>
</action>

默认的struts-default.xml配置设置了一个默认的拦截器栈,可以在大多数应用程序中运行的很好。
更多详细信息,请查看拦截器部分。
1.3.3.7 配置Action
action映射是框架中的基本工作单元。从本质上看,action把一个标识符映射到一个处理器类。当一个请求与action的名称匹配时,框架使用映射来确定如何处理请求。
1.3.3.7.1 Action映射
action映射可以指定一组result类型、一组异常处理器、一组拦截器栈。只有name属性是必须的,其它属性也可以在包范围内提供。
A Logon Action
<action name="Logon" class="tutorial.Logon">
    <result type="redirectAction">Menu</result>
    <result name="input">/Logon.jsp</result>
</action>
1.3.3.7.2 Action名称
在Web应用程序中,name属性会被匹配为浏览器(或其它HTTP客户端)请求的位置的一部分。框架会舍去主机、应用程序名称、以及扩展名,并匹配中间的action名称部分。因此,以下请求会映射到Welcome action:http://www.planetstruts.org/struts2-mailreader/Welcome.do

在一个应用程序中,一个到action的链接通常是由Struts标签产生的。标签可以通过名称来指定action,框架会渲染默认的扩展名以及其它所需的东西。表单也可以直接提交到一个Struts Action name,而不是原生的URI。
A Hello Form
<s:form action="Hello">
    <s:textfield label="Please enter your name" name="name"/>
    <s:submit/>
</s:form>

包含斜杠的Action名称
如果你的Action名称中有斜杠(例如,<action name="admin/home" class="tutorial.Admin"/>),你需要通过在struts.xml文件中指定<constant name="struts.enable.SlashesInActionNames" value="true"/>常量来明确允许使用斜杠。查看JIRA Issue WW-1383,其中讨论了把这个属性设置为true的副作用。

包含圆点和破折号的Action名称
尽管action名称相当灵活,但是在使用圆点(例如create.user)或破折号(例如my-action)时,你必须要注意点。虽然圆点符号到现在为止没有已知的副作用,但是破折号会导致为某些标签或主题生成的JavaScript的问题。请小心使用,并尝试使用骆驼拼写法的Action名称(例如createUser)或下划线(例如my_action)。
1.3.3.7.3 Action方法
处理器类的默认入口方法是由Action接口定义的。
Action interface
public interface Action {
    public String execute() throws Exception;
}

Action接口的实现是可选的。如果一个Action没有实现Action接口,框架会通过反射查找一个execute方法。

有时候,开发人员要创建多个Action入口点。例如数据访问Action的情况下,开发人员想要分割create、retrieve、update、delete入口点。不同的入口点可以通过method属性来指定。

<action name="delete" class="example.CrudAction" method="delete">
    ...

如果没有execute方法,且在配置中没有指定其它方法,那么框架会抛出一个异常。
1.3.3.7.4 通配符方法
有些时候,一组action映射会共享一个相同的模式。例如,所有的edit action可能都会以edit开头,并在Action类中调用edit方法,而delete action可能也使用相同的模式,但是调用delete方法。

你可以编写一个通配符映射,而不是为使用这个模式的每个action类各编写一个单独的映射。
<action name="*Crud" class="example.Crud" method="{1}">
    ...
在此,对editCrud的引用将会调用Crud Action类的实例中edit方法。同样,对deleteCrud的引用将会调用delete方法。

另一个常用的方法是给方法名加个后缀,在它后面设置一个感叹号,下划线,或其它特殊字符。
(1)action=Crud_input
(2)action=Crud_delete

要使用后缀通配符,只需要移动星号,并添加一个下划线。
<action name="Crud_*" class="example.Crud" method="{1}">

从框架的角度来看,通配符映射创建了一个新的虚拟映射,它的所有属性与约定的静态映射相同。因此,你可以把扩展的通配符名称作为validation名称,类型转换,信息资源文件,就像它是一个Action名称(确实也是这样)。
(1)Crud_input-validation.xml
(2)Crud_delete-conversion.xml

如果通配符方法映射使用一个“不能渲染嵌套对象:文件(在action名称中,通配符方法将会与另一种灵活的方法来映射重叠,动态方法映射。要使用action名称包括)没有找到”的字样,那么就要在应用程序配置中把struts.enable.DynamicMethodInvocation设置为FALSE。
1.3.3.7.5 动态方法调用
在WebWork 2中有一个特性,可以通过“!”(感叹号)来调用一个execute之外的方法。在WebWork中,它并没有真正的名称。在S2的讨论中,我们创造了“动态方法调用”术语来描述WW/S2如何使用感叹号。

动态方法调用(DMI)将会使用action名称中“!”后面的字符串作为需要调用的方法的名称,而不是execute。对Category!create.action的引用,会使用Category的action映射,并调用create方法。

对于Struts 2,我们添加了一个开关来禁用DMI,主要出于两个原因。首先,如果使用POJO的action,DMI可能会导致安全问题。其次,DMI与我们从Struts 1中带来的(之前是Cocoon中的)通配符方法功能重叠。如果你有安全问题,或者想一起使用“!”字符与通配符方法的action,那么要在应用程序配置中把struts.enable.DynamicMethodInvocation设置为FALSE。

框架支持DMI,就像WebWork 2,但是DMI的实现方式有问题。从本质上看,代码在action名称中扫描“!”字符,如果找到一个“!”字符,让框架调用其它方法而不是execute。其它方法被调用时,它会对execute方法使用相同的配置,包括验证。框架会认为它是在调用Category action的execute方法。

而通配符方法功能的实现是不同的。当一个通配符方法的action被调用时,框架会视为匹配的action已经硬编码在配置中了。框架会认为它是在执行Category!create action,并且知道它是在执行对应的Action类的create方法。相应的,我们可以为通配符方法的action映射添加它自己的验证,信息资源,和类型转换,就像传统的Action映射。出于这个原因,通配符方法是首选的。

在Struts 2.3中,添加了一个选项来限制DMI可以调用的方法。首先,在<package>元素中设置strict-method-invocation="true"属性。然后,在<action>中用逗号分隔的方法名列表来指定<allowed-methods>。对其它任何方法的请求都会被拒绝。如果你在action中指定了method属性,你不需要在<allowed-methods>把它列出来。
1.3.3.7.6 默认的ActionSupport
如果action映射中的class属性为空,就会使用默认的com.opensymphony.xwork2.ActionSupport类。
<action name="Hello">
    // ...
</action>

ActionSupport类有一个返回success的execute方法,以及一个返回input的input方法。要指定一个不同的类作为默认的Action类,需要设置default-class-ref包属性。

关于通配符的更多详情,请查看通配符映射。
1.3.3.7.7 默认的Post-Back
一个好的做法是链接到一个action而不是页面,链接到封装了需要渲染哪个服务器页面的action,并确保能够在页面显示之前触发Action类。

另一种常用的工作流策略是首先通过另一个方法来渲染一个页面,例如input方法,然后这个页面提交给默认的execute方法。

一起使用这两种策略创造了一个使用不指定action的post-back表单的机会。这个表单只是简单地提交回创建这个表单的action。

Posting Back
<s:form>
    <s:textfield label="Please enter your name" name="name"/>
    <s:submit/>
</s:form>
1.3.3.7.8 默认的Action
通常,如果请求了一个action,并且框架不能把这个请求映射到一个action name上,那么result将会是通用的“404 - Page not found”错误。但是,如果你更喜欢用一个综合性的action来处理任何不匹配的请求,那么你可以指定一个默认的action。如果没有其它匹配的action,那么就会使用这个默认的action。
<package name="Hello" extends="action-default">
    <default-action-ref name="UnderConstruction"/>
    <action name="UnderConstruction">
        <result>/UnderConstruction.jsp</result>
    </action>
    ...

对于默认的action,没有特殊的要求。每个包都可以有它自己的默认action,但是每个命名空间中只能有一个默认的action。

一个命名空间一个默认的action
应该把默认的action特性设置为每个命名空间只有一个默认的action。如果具有相同命名空间的多个包都声明了默认的action,那么旧不能保证哪个action是默认的。
默认的通配符
使用通配符是使用默认的action的另一种方式。在配置的最后面的通配符action可以捕捉所有不匹配的引用。
<action name="*">
  <result>/{1}.jsp</result>
</action>

在需要一个新的action时,只是添加一个存根页面。
把这样一个“一网打尽”的通配符映射放在配置的最后面是很重要的,这样它才不会试图映射所有请求。
1.3.3.8 通配符映射
随着应用程序规模的增长,action映射数量也随之增长。通配符可以用于把相似的映射组合为一个更通用的映射。

解释通配符的最佳方式是展示一个示例,并从头到尾看一遍它是如何工作的。以下示例修改了约定的映射,使用通配符来匹配所有以/edit为开头的页面:
<action name="/edit*" class="org.apache.struts.webapp.example.Edit{1}Action">
    <result name="failure">/mainMenu.jsp</result>
    <result>{1}.jsp</result>
</action>

name属性中的*允许映射匹配到以下请求URI:/editSubscription、editRegistration,或其它任何以/edit为开头的URI,但是,不会匹配/editSubscription/add。通过替换{1},与通配符匹配的URI部分将会被替换到action映射的各个属性以及它的action result中。对于其余的请求,框架将会查看包含新值的action映射以及它的action result。

映射将以它们在框架的配置文件中出现的顺序来匹配请求。如果有多个模式匹配,那么将会使用最后一个,因此,比较不特殊的模式必须出现在比较特殊的模式之前。但是,如果请求的URL能够匹配到一个没有任何通配符的路径,那么,将会执行没有通配符的匹配项,而且顺序是不重要的。另外,请注意,通配符并不是贪婪的,这意味着直到第一个符合的字符串模式的出现它们才会匹配。例如,考虑以下映射:
<action name="List*s" class="actions.List{1}s">
    <result>list{1}s.jsp</result>
</action>
这个映射当前只会作用于ListAccounts URI,而不会作用于ListSponsors,因为ListSponsors会进入这个配置:
<action name="ListSpons" class="actions.ListSpons">
    <result>listSpons.jsp</result>
</action>

通配符模式可以包含以下特殊字符中的一个或多个:
*    匹配0个或多个字符,不包括斜杠“/”字符
**    匹配0个或多个字符,包括斜杠“/”字符
\字符    使用反斜杠作为转义字符,因此:“\*”匹配星号*字符;“\\”匹配反斜杠“\”字符

在action映射和action result中,通配符的匹配值可以被标记{N}访问,其中,N为从1到9的数字,表示用哪个通配符的匹配值来替换。整个请求URI可以被{0}标记访问。

另外,action映射和action result属性将会在它们的value属性中接收通配符匹配的字符串,例如:
<action name="/edit/*" class="org.apache.struts.webapp.example.Edit{1}Action">
    <param name="id">{1}</param>
    <result>
        <param name="location">/mainMenu.jsp</param>
        <param name="id">{1}</param>
    </result>
</action>

查看通配符方法
1.3.3.8.1 命名空间中的参数
从Struts 2.1+开始,命名空间模式可以被提取出来作为请求参数,并绑定到action。要使用这个功能,需要在struts.xml中设置以下常量:
<constant name="struts.patternMatcher" value="namedVariable"/>

在定义命名空间的地方,命名空间的定义可以包含{PARAM_NAME}模式,这个模式将会对请求的URL进行评估,并提取为参数,例如:
@Namespace{"/users/{userID}");
public class DetailsAction exends ActionSupport {
    private Long userID;
    public void setUserID(Long userID) {...}
}
如果请求的URL是/users/10/detail,那么DetailsAction将会被执行,并且userID字段将会被设置为10.

在一个时间上,只能使用一个PatternMatcher的实现。Struts 2中包含的两种实现是互斥的。你不能在同一个应用程序中使用通配符和命名变量模式,如果需要这样的话,你需要创建一个自定义PatternMatcher实现。
一些标签不能100%兼容命名空间中的变量。例如,他们可能会把文字的命名空间写入HTML中(例如/{user}/2w),而不是请求中使用的路径(例如/brett/24)。这通常会影响属性,这些属性试图推测一个action的命名空间(例如,Form标签和Action标签的action=)。这个问题可以通过直接使用带有相对路径或绝对路径的HTML标签来避免。

通过一个自定义的ActionMapper,可以实现类似的功能。ActionMapper需要解析命名空间和请求本身来设置匹配的action的参数。默认的ActionMapper负责调用PatternMatcher。
1.3.3.8.2 action名称之后的参数
要在action名称之后的URL中使用参数,需要设置:
<constant name="struts.enable.SlashesInActionNames" value="true"/>
<constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/>

然后,action映射看起来像:
<package name="edit" extends="struts-default" namespace="/edit">
    <action name="/person/*" class="org.apache.struts.webapp.example.EditAction">
        <param name="id">{1}</param>
        <result>/mainMenu.jsp</result>
    </action>
</package>
当请求了/edit/person/123 URL时,EditAction将会被调用,并且它的id字段将会被设置为123。
1.3.3.8.3 高级通配符
从2.1.9+开始,可以在action名称中定义正则表达式。要使用这种形式的通配符,需要设置以下常量:
<constant name="struts.enable.SlashesInActionNames" value="true"/>
<constant name="struts.mapper.alwaysSelectFullNamespace" value="false"/>
<constant name="struts.patternMatcher" value="regex" />

正则表达式可以有两种形式:最简单的一种是{FIELD_NAME},在这种情况下,将会用匹配的文本来填充action中的FIELD_NAME字段,例如:
<package name="books" extends="struts-default" namespace="/">
    <action name="/{type}/content/{title}" class="example.BookAction">
        <result>/books/content.jsp</result>
    </action>
</package>
在这种情况下,如果请求了/fiction/content/Frankenstein URL,BookAction的type字段将会被设置为fiction,并且title字段将会被设置为Frankenstein。

正则表达式也可以是{FIELD_NAME:REGULAR_EXPRESSION}形式的。正则表达式是一个正常的Java正则表达式。例如:
<package name="books" extends="struts-default" namespace="/">
    <action name="/{type}/{author:.+}/list" class="example.ListBooksAction">
        <result>/books/list.jsp</result>
    </action>
</package>
在这个示例中,如果请求了/philosophy/AynRand/list URL,ListBooksAction的type字段将会被设置为philosophy,并且author将会被设置为AynRand。“.+”表示出现1个或多个除“\n”之外的任何单个字符。

仍然可以使用{X}标记来访问匹配组,例如:
<package name="books" extends="struts-default" namespace="/">
    <action name="/books/{ISBN}/content" class="example.BookAction">
        <result>/books/{1}.jsp</result>
    </action>
</package>
1.3.3.9 配置Result
在一个action类的方法结束时,会返回一个字符串。此字符串的值用于选择result元素。一个action映射通常会有一组result,表示不同的可能的输出。ActionSupport基类中定义了一组标准的result标记。
Predefined result names
String SUCCESS = "success";
String NONE    = "none";
String ERROR   = "error";
String INPUT   = "input";
String LOGIN   = "login";
当然,应用程序可以定义其它result标记来匹配特殊情况。

在一个action类的方法中返回ActionSupport.NONE(或null)会导致result的处理被忽略。如果一个action完全处理了对result的处理,这相当有用,例如直接把result写入HttpServletResponse的OutputStream。
1.3.3.9.1 Result元素
result元素有两个任务。首先,它提供了一个逻辑名。Action可以传回一个标记,例如success或error,而不需要了解任何其它实现细节。其次,result元素提供了一个result类型。大多数result只是简单的转发到服务器页面或模板,但是其它Result类型可以用于处理更多有趣的事情。

智能默认值
每个包都可以设置一个默认的result类型,在一个result元素中什么都没指定时使用。如果一个包扩展了另一个包,子包可以设置它自己的默认result或继承父包的默认result。
Setting a default Result Type
<result-types>
    <result-type name="dispatcher" default="true"
        class="org.apache.struts2.dispatcher.ServletDispatcherResult" />
</result-types>

如果没有指定type属性,框架会使用默认的dispatcher类型,用于转发到其它web资源。如果资源是一个JavaServer Page,那么容器将会使用JSP引擎渲染这个JSP。

另外,如果没有指定name属性,框架会给它提供一个success名称。

使用这些智能默认值,最常用的result类型也会变成最简单的。
Result element without defaults
<result name="success" type="dispatcher">
    <param name="location">/ThankYou.jsp</param>
</result>

A Result element using some defaults
<result>
    <param name="location">/ThankYou.jsp</param>
</result>

param标签用于设置Result对象的属性。最常设置的属性为location,通常用于指定web资源的路径。param属性是另一个智能默认值。
Result element using more defaults
<result>/ThankYou.jsp</result>


把智能默认值与其它result混合在一起,更易于查看关键路径。
Multiple Results
<action name="Hello">
    <result>/hello/Result.jsp</result>
    <result name="error">/hello/Error.jsp</result>
    <result name="input">/hello/Input.jsp</result>
</action>

通过添加一个name="*"的result,可以配置一个特殊的other result。这个result只有在没有找到名称匹配的result时才会被选择。
'*' Other Result
<action name="Hello">
    <result>/hello/Result.jsp</result>
    <result name="error">/hello/Error.jsp</result>
    <result name="input">/hello/Input.jsp</result>
    <result name="*">/hello/Other.jsp</result>
</action>
name="*"不是通配符模式,它是一个特殊的名称,只有在没有找到完全匹配的result时才会被选择。
在大多数情况下,如果一个action返回一个无法识别的result name,这可能是一个程序错误,应该被修复。
1.3.3.9.2 全局Result
通常,result都是嵌套在action元素中的,但是,一些result需要应用到多个action。在一个安全的应用程序中,客户端可能会尝试访问一个没有授权的页面,并且很多action可能需要访问logon result。

如果action需要共享result,可以为每个包定义一组全局的result。框架首先会查找嵌套在action中的局部result,如果没有找到匹配的局部result,就会检查全局的result。
Defining global results
<global-results>
    <result name="error">/Error.jsp</result>
    <result name="invalid.token">/Error.jsp</result>
    <result name="login" type="redirectAction">Logon!input</result>
</global-results>

关于result的更多详情,请查看Result类型。
1.3.3.9.3 动态Result
result可能在执行时才可知,考虑一下基于状态机制的执行流的实现,下一个状态可能依赖于表单输入元素、session属性、用户角色、月相等的任意组合。也就是说,在配置时,可能不能确定下一步操作,输入页面,等等。

通过使用EL表达式来访问Action的属性,可以从相应的Action实现中获取Result值,就像Struts 2的标签库。因此,对于以下给定的Action片段:
FragmentAction implementation
private String nextAction;

public String getNextAction() {
    return nextAction;
}
你可能会这样定义result:
FragmentAction configuration
<action name="fragment" class="FragmentAction">
    <result name="next" type="redirectAction">${nextAction}</result>
</action>

如果FragmentAction的一个方法返回next,result的实际值将会是FragmentAction的nextAction属性的值。因此,基于需要的状态信息,可以计算出nextAction的值,然后在运行时传递给next的redirectAction。

扩展讨论,请查看result配置中的参数。
1.3.3.10 Unknown处理器
Unknown处理器栈从Struts 2.1开始可用。
1.3.3.10.1 Unknown处理器
Unknown处理器是实现了com.opensymphony.xwork2.UnknownHandler接口的类,并且在执行一个未知的action、result或方法时,被框架调用。要定义Unknown处理器,需要创建一个类,实现上述接口,并在struts.xml中添加一个bean定义:
<bean type="com.opensymphony.xwork2.UnknownHandler" name="handler"
    class="myclasses.SomeUnknownHandler"/>
1.3.3.10.2 Unknown处理器栈
多个unknown处理器可以通过unknown-handler-stack标签来定义。
<bean type="com.opensymphony.xwork2.UnknownHandler" name="handler1"
    class="com.opensymphony.xwork2.config.providers.SomeUnknownHandler"/>
<bean type="com.opensymphony.xwork2.UnknownHandler" name="handler2"
    class="com.opensymphony.xwork2.config.providers.SomeUnknownHandler"/>

<unknown-handler-stack>
    <unknown-handler-ref name="handler1" />
    <unknown-handler-ref name="handler2" />
</unknown-handler-stack>

当多个unknown处理器像上面这样堆叠在一起时,它们会按照指定的顺序被调用,就像一个unknown处理器会被调用一样(在执行一个未知的action、result或方法时),直到其中一个处理器处理了请求的action。
1.3.3.10.3 Unknown处理器管理器
默认的处理unknown处理器栈的类是com.opensymphony.xwork2.DefaultUnknownHandlerManager,通过实现com.opensymphony.xwork2.UnknownHandlerManager接口,并设置
struts.unknownHandlerManager属性,可以提供这个类的一个自定义实现。
1.3.3.11 配置异常
异常映射是一个强大的特性,用于处理Action类抛出的异常。其核心思想是在Action方法中抛出的异常能够被自动捕捉,并映射到一个预定义的Result。这个声明性策略对于框架来说特别有用,就像Hibernate和Acegi,抛出一个RuntimeException。

与框架的一些其它部分一样,需要一个拦截器来激活异常映射功能。以下是一个来自struts-default.xml的已经激活了异常映射的片段。
snippet of struts-default.xml
...
<interceptors>
    ...
    <interceptor name="exception"
        class="com.opensymphony.xwork.interceptor.ExceptionMappingInterceptor"/>
    ...
</interceptors>

<interceptor-stack name="defaultStack">
    <interceptor-ref name="exception"/>
    <interceptor-ref name="alias"/>
    <interceptor-ref name="servlet-config"/>
    <interceptor-ref name="prepare"/>
    <interceptor-ref name="i18n"/>
    <interceptor-ref name="chain"/>
    <interceptor-ref name="debugging"/>
    <interceptor-ref name="profiling"/>
    <interceptor-ref name="scoped-model-driven"/>
    <interceptor-ref name="model-driven"/>
    <interceptor-ref name="fileUpload"/>
    <interceptor-ref name="checkbox"/>
    <interceptor-ref name="static-params"/>
    <interceptor-ref name="params"/>
    <interceptor-ref name="conversionError"/>
    <interceptor-ref name="validation">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
    <interceptor-ref name="workflow">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
</interceptor-stack>
...

要使用异常映射,我们只需要简单地把异常映射到指定的Result。框架提供了两种方式来声明一个异常映射<exception-mapping/>,全局的或特定于某个action的映射。exception mapping元素包括两个属性:
exception和result。

在声明一个异常映射的时候,拦截器会在抛出异常和声明异常的类之间进行匹配,找到最接近的calss实例。拦截器会检测所有声明的应用于action映射的映射。如果有一个映射匹配的话,就会处理这个Result,就像它是被Action返回的一样。

处理过程遵循与Action返回的Result一样的规则。首先,它会查找局部action映射的Result,如果没有找到,才会查找全局的Result。

以下是一个全局和局部异常映射的示例。
snippet from struts.xml
<struts>
    <package name="default">
        ...
        <global-results>
            <result name="login" type="redirect">/Login.action</result>
            <result name="Exception">/Exception.jsp</result>
        </global-results>

        <global-exception-mappings>
            <exception-mapping exception="java.sql.SQLException" result="SQLException"/>
            <exception-mapping exception="java.lang.Exception" result="Exception"/>
        </global-exception-mappings>
        ...
        <action name="DataAccess" class="com.company.DataAccess">
            <exception-mapping exception="com.company.SecurityException" result="login"/>
            <result name="SQLException" type="chain">SQLExceptionAction</result>
            <result>/DataAccess.jsp</result>
        </action>
        ...
    </package>
</xwork>

在上述示例中,以下是基于每个异常所发生:
(1)java.sql.SQLException会链接到SQLExceptionAction(action映射没有显示出来);
(2)com.company.SecurityException会重定向到Login.action;
(3)其它继承java.lang.Exception的异常会返回/Exception.jsp页面。
1.3.3.11.1 ValueStack中的异常值
默认情况下,ExceptionMappingInterceptor会把以下值添加到值栈中:
exception    异常对象自身
exceptionStack    堆栈跟踪中的值

Sample JSP using Error and Exception Values
<h2>An unexpected error has occurred</h2>
<p>
    Please report this error to your system administrator
    or appropriate technical support personnel.
    Thank you for your cooperation.
</p>
<hr/>
<h3>Error Message</h3>
<s:actionerror/>
<p>
    <s:property value="%{exception.message}"/>
</p>
<hr/>
<h3>Technical Details</h3>
<p>
    <s:property value="%{exceptionStack}"/>
</p>
1.3.3.11.2 构造函数中的异常
全局的异常映射是为action方法(例如execute)抛出的异常而设计的,从构造函数中抛出的异常不会被全局异常映射处理。

猜你喜欢

转载自hanyuan8407.iteye.com/blog/1759471
今日推荐