Struts2 官方教程:动作配置(Action Configuration)

动作映射是框架中基本的“工作单元”。本质上,动作映射一个标识符来操纵类。当一个请求匹配了动作的名称时,框架使用映射来决定如何处理这个请求。

动作映射(Action Mappings)

动作映射可以指定一个结果类型的集合,一个异常处理程序的集合,以及一个拦截器栈。仅要求有name属性。其它属性也可以再package区域中提供。

一个Logon 动作
<action name="Logon" class="tutorial.Logon">
    <result type="redirectAction">Menu</result>
    <result name="input">/Logon.jsp</result>
</action>

当使用了Convention Plugin时,动作映射可以用注记来配置:

一个带有注记的Logon动作
package tutorail
@Action("Logon") //实际上并非必须的,按惯例添加
@Results(
    @Result(type="redirectAction",location="Menu"),
    @Result(name="input",location="/Logon.jsp")
)
public class Logon{

动作名称(Action Names)

在一个网页应用中,name属性是作为被一个浏览器(或者其它HTTP客户端)所请求的location的一部分被匹配。框架会丢弃主机和应用的名称及扩展名,并且匹配在中间的部分:动作的名称。这样,一个对http://www.planetstruts.org/struts2-mailreader/Welcome.action 的请求会匹配到Welcome动作。
在应用中,一个到动作的链接通常被一个Struts标记所生成。这个标记可以通过name来指定动作,框架会渲染默认的扩展名以及其它任何需要的东西。表单也可能回直接提交一个Struts动作名称(而不是一个“原始”的URI)。

一个 Hello 表单
<s:form action="Hello">
    <s:textfield label="Please enter your name" name="name"/>
    <s:submit/>
<s:form>

带斜杠的动作名称(Action Names With Slashes)

如果动作名称中有斜杠(例如,<action name=”admin/home” class=”tutorial.Admin”/>),那么需要通过在struts.xml文件中指定<constant name=”struts.enable.SlashesInActionNames” value=”true”/>这个常量,来允许在动作名称中存在斜杠。

带点和短线的动作名称(Action Names With Dots and Dashes)

尽管动作命名非常灵活,但也应当在使用句点时多加注意(例如create.user)以及/或者短线(例如 my-action)。句点目前没有已知的副作用,而破折号对特定的标记和主题生成的JavaScript会引起问题。带着小心使用,并且总是尝试使用驼峰式动作名称(例如createUser)或者下划线(例如 my_action)。

被允许的动作名称(Allowed action names)

DefaultActionMapper 是使用预定义正则表达式来检查,动作名称是否匹配了允许的名称。默认的正则表达式是如下定义的:[a-zA-Z0-9._!/-]*- 如果某些部分并不匹配读者的动作命名规则,可以定义自己的正则表达式,并且使用名为 struts.allowed.action.names 这个常量来重写默认的正则表达式,例如:

<struts>
    <constant name="struts.allowed.action.names" value="[a-z{}]"*/>
</struts>

注意:请知悉,动作名称不匹配正则表达式,会引发异常。

动作方法(Action Methods)

默认控制类的入口方法是通过动作接口来定义。

Action interface
public interface Action{
    public String execute() throws Exception;
}

实现Action interface 是可选的。如果动作未被实现,框架会使用反射机制来寻找execute方法。
有时候,开发者希望为一个动作创建多于一个的入口。例如,在一个 data-access 动作的情形中,开发者可能希望创建,恢复,更新以及删除有不同的入口。不同的入口可以用method属性来指定。

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

如果没有execute方法,并且在配置文件中没有指定其它方法,框架会抛出一个异常。
Convetion Plugin 允许使用注记方法:

注记的动作方法
@Action("crud")
public class CrudAction{
    @Action("delete")
    public String delete(){
    ……

通用方法(Wildcard Method)

很多时候,一系列动作映射会公用一个模式。例如,读者所有的edit动作会以单词“edit”开始,并且调用动作类中的edit方法。delete动作也使用相同的模式,但调用的是delete方法。
为每个动作类使用这个模式书写不同的映射,可以写一次,作为通用匹配:

<acton name="*Crud" class="example.Crud" method="{1}">

这里,一个对“editCrud”的引用会让一个Crud动作类的实例调用edit方法。类似地,一个对“deleteCrud”的引用会调用delete方法。
另一个常见的方法是把方法名称作为后缀添加,并且用感叹号、下划线、或其它字符来连接。

  • “action=Crud_input”
  • “action=Crud_delete”

为了使用后缀通用,只要移动星号并且添加下划线。

<action name="Crud_*" class="example.Crud" method="{1}">

从框架的角度,通用匹配创建了一个新的“虚”匹配,所有相同的属性静态匹配。作为结果,读者可以使用扩展的通用名称作为验证(validation)、类型转换(type conversion)、以及信息资源文件(message resource files)的名称,就像是使用动作的名称一样。

  • Crud_input-validation.xml
  • Crud_delete-conversion.xml

如果通用方法映射在动作名称中使用了一个感叹号,通用方法会与另一个灵活的映射方法所重叠,即动态方法调用。为了使用带有感叹号字符的动作名称,要在应用的配置文件中,设置 struts.enable.DynamisMethodInvocation 的值为 FALSE 。

动态方法调用(Dynamic Method Invocation)

在Struts2中有个内嵌的特性,使得可以通过感叹号字符调用不是execute的另一个方法。这被称为“动态方法调用”,即DMI。
DMI使用在动作名称中感叹号字符之后的字符串,作为希望调用的方法名(代替execute)。一个对“Category!create.action”的调用,意思是使用“Category”动作映射,但调用create方法。
另一种使用DMI的途径是用“method:”前缀向HTTP提供参数。例如,在URL中,可以是“Category.action?method:create=foo”,参数值会被忽略。在POST请求可以被使用,例如用一个隐藏参数(<s:hidden name=”method:create” value=”foo” />)或者放在一个按钮中(<s:submit method=”create” />)
对于Struts2,出于两个原因,对禁用DMI添加了开关。首先,如果POJO动作被使用,DMI会引起安全问题。其次,DMI与我们从Struts1时代继承下来的通用方法调用特性重复。如果读者有安全顾虑,或者希望在通用方法中使用感叹号字符,那么请在应用配置中将struts.enable.DynamicMethodInvocation 设置为 FALSE 。
框架是支持DMI的,但DMI实现的方式存在一些问题。本质上,代码以感叹号字符为基准检查动作名称,找到一个动作后,诱导框架调用非execute的另一个方法。另一个方法被调用了,但它使用execute方法相同的配置,包括验证过程。框架自身是“相信”自己正在调用Category动作的execute方法。
通用方法特性的实现是不一样的。当通用方法动作被调用时,框架会像匹配到的动作已经在配置中被硬编码那样行动。框架“相信”它正在执行动作Category!create,并且“知道”自己正在执行对应动作类的create方法。因此,可以为通用方法动作映射添加它自己的验证、消息资源、以及类型转换,就像习惯的动作映射那样。处于这个原因,推荐使用通用方法。

严格DMI(Strict DMI)

在Struts2.3,添加了一个限制DMI可以调用方法的选项。首先在<package>元素中,设置属性 strict-method-invocation=”true”。这告诉Struts来拒绝任何未通过method属性或者<allowed-method>标记被显式允许的方法。然后,在<action>中,以逗号分隔的方法名列表形式来指定<allowed-methods>。(如果读者为动作指定了一个method属性,则不需要将这个方法在<allowed-methods>中列出)
请注意,可以在没有配置 strict-method-invocation 的情况下指定<allowed-methods>。这个限制的读取仅用于拥有 <allowed-methods>的特定动作。

示例struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>

  <constant name="struts.enable.DynamicMethodInvocation" value="true"/>

  <package name="default" extends="struts-default" strict-method-invocation="true">

    <action name="index" class="org.apache.struts2.examples.actions.Index">
        <result name="success" type="redirectAction">hello</result>
    </action>

    <action name="hello" class="org.apache.struts2.examples.actions.HelloAction">
        <result name="success">/WEB-INF/content/hello.jsp</result>
        <result name="redisplay" type="redirectAction">hello</result>
        <allowed-methods>add</allowed-methods>
    </action>

  </package>
</struts>

严格的方法调用(Strict Method Invocation)

在Struts2.5 ,严格的动态方法调用被扩展,并且称为“严格方法调用”, Strict Method Invocation(SMI)。读者可以想象,DMI是个边防警察,而SMI是关注内部的“税务警察”。在这个版本中,SMI默认启用(在struts-default 这个package中, strict-method-invocation 属性设置为true;读者可以选择为每个package禁用此选项——因为没有为整个应用全局禁用SMI的开关。为了获得新配置选项的益处,请使用最新的DTD定义:

Struts 2.5 DTD
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
...
</struts>

SMI以下述方式工作:

  • 为每个动作定义<allowed-methods>/@allowedMethods ——不需要开启,SMI是启用的;但仅对于部分动作有效(加上<gloabal-allowed-methods/>的)
  • SMI被启用、但没有<allowed-methods>/@AllowedMethod被定义——SMI可用,但仅对<global-allowed-methods/>有效
  • SMI被禁用——调用任何动作任何匹配了默认正则表达式的方法都是允许的。默认正则表达式:[A-Za-z0-9_$]*

读者可以使用类似下面这样的常量来重新定义RegEx

当在动作的定义中使用通用映射,SMI以两种途径工作:

  • SMI被禁用:任何通用方法会用默认正则表达式代替,即:会被翻译成 allowedMethod=”regex:perform([A-Za-z0-9_$]*)”。
  • SMI被启用:没有通用置换会发生,必须严格地定义哪个方法被读取,通过注记或者<allowed-method/>标记。

可以为每个<action/>配置SMI,使用<allowed-methods/>标记,或者通过@AllowedMethod注记,为每个<package/>添加<global-allowed-methods/> ,查看下方的例子:

通过struts.xml使用的SMI
<package ...>
  ...
  <global-allowed-methods>execute,input,back,cancel,browse</global-allowed-methods>
  ...
  <action name="Bar">
    <allowed-methods>foo,bar</allowed-methods>
  </action>
  ...
</package>
通过在动作类层级使用的SMI
@AllowedMethods("end")
public class ClassLevelAllowedMethodsAction{
    public String execute() {
        return ...
    }
}
在package级别(写在 package-info.java中)注记而使用的SMI
@org.apache.struts2.convention.annotation.AllowedMethods({"home","start"})
package org.apache.struts2.convention.actions.allowedmethods;

被允许的动作可以这样定义:

  • 字面,即在xml文件中:execute,cancel ,或者在注记中:{“execute”,”cancel”}
  • 模式:通过通用映射使用,即 <action … method=”do{2}”/>
  • 正则表达式: <global-allowed-methods>execute,input,cancel,regex:user([A-Z]*)</global-allowed-methods>

请知悉:当使用读者自己的 Configurationprovider 时,设置允许方法的逻辑,是在provider内置的 XmlConfigurationProvider 和 PackageBasedActionConfigBuilder 中定义。并且,读者必须在自己的代码中重现这样的逻辑,保证默认情况下,即使SMI被禁用,也仅有execute方法是被允许的。

默认动作支持(ActionSupport Default)

如果在动作映射中的类属性是空白的,那么com.opensymphony.xwork2.ActionSupport类会被作为默认的。

<action name="Hello">
    //...
</action>

默认回发(Post-Back Default)

好的实践,是链接到动作而不是链接到页面。链接到动作封装了渲染的服务页面,并且保证了动作类会在页面渲染前行动。
另一个常见的工作流策略是首先使用可选的方法渲染一个页面,类似input,然后通过submit来调用默认的execute方法。
一起使用这两种策略,创建了使用并不指定动作的“回发”表单的机会。表单仅仅向创建它的动作提交。

回发
<s:form>
    <s:textfield label="Please enter your name" name="name"/>
    <s:submit/>
</s:form>

默认动作(Action Default)

通常,如果一个动作被请求,并且框架无法将请求映射到动作的名称时,结果会是常见的“404-Page not found”错误。但,如果读者选择一个公用动作来处理任何未匹配的请求,可以指定一个默认动作。如果没有其它的动作被匹配,默认动作会被使用。

<package name="Hello" extends="action-default">
    <default-action-ref name="UnderConstruction"/>
    <action name="UnderConstruction">
        <result>/UnderConstruction.jsp</result>
    </action>
</package>

对于默认动作,没有特定的请求。每个package可以拥有自己的默认动作,但每个namespace只能拥有一个默认动作。
默认动作特性应当被设定,确保每个namespace仅有一个默认动作。如果读者有多个package,为同一个namespace声明默认动作,那么无法保证默认会调用哪个动作。

默认的通用(Wildcard Default)

使用通用方法是另一种实现默认动作的途径。一个在配置末尾的通用动作可以被使用来捕获未匹配的引用。

<action name="*">
    <result>/{1}.jsp</result>
</action>

当一个新动作被需要时,那么只要添加一个对应动作名的页面就行咯。
放置像这样的一个“捕获所有”的通用匹配很重要,这样它不会尝试匹配每个请求!

附个人实践笔记

翻译本篇教程的初衷,是为了尝试在 动作1(“登陆”)对应的页面中,添加一个能够进行动作2(“注册输入”)的按钮,以跳转至注册页面。几经尝试,未能实现原本期望的,访问 动作2.action。
跳转到某个动作对应的页面,有两种访问的方式:访问动作.action,或直接访问页面;渲染的页面都完全一样。但,以访问动作.action形式访问时,所有的提交按钮都只让动作类执行同一系列方法进行处理:要么是execute,要么是别的指定方法(在之前还有prepare)。
因此,按照原有的想法,需要在这个执行处理的方法(例如execute)中,识别按下了哪个按钮,以进入不同的判断分支,最终返回不同的字符串(登陆返回的是“success”,而选择注册则要返回“registry”)。但是,在execute中,是无法接直接接收到button属性的,也就意味着execute无法直接判断,按下了哪个按钮——所以多少个按钮都是一个效果。
一种实现的方式是:

  • 在表单中添加隐藏的一个输入域 input ;
  • 通过javascript在点击按钮时进行判断,并将上述的隐藏输入域的输入值进行修改;

隐藏域的输入值被作为表单输入的一部分而提交给动作类,这样向execute方法提供了用于判断的属性。但我测试无法成功:写出的javascript函数,在<st:submit>之中的onclick无法调用。
否则,只能简单粗暴地添加直接访问页面的链接(Registry.jsp)来实现。

对于通用方法调用,有一点需要注意,就是在开启了SMI的情况下,要将自己希望调用的方法都放在 action 的 allowed-methods 之中,才能实现调用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值