Struts 2.x权威指南
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.5.8 使用通配符

在配置<action .../>元素时,需要指定name、class和method属性,其中name属性支持通配符,然后可以在class、method属性中使用表达式。这种使用通配符的方式是另一种形式的动态方法调用。当我们使用通配符定义Action的name属性时,相当于一个<action.../>元素定义多个逻辑Action。

看下面的struts.xml配置文件代码。

程序清单:codes\03\3.5\wildcard\WEB-INF\src\struts.xml

<?xml version="1.0" encoding="GBK" ?>
<!-- 指定Struts 2配置文件的DTD信息 -->
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <!-- Struts 2的Action必须放在指定的包空间下定义 -->
    <package name="struts2qs" extends="struts-default">
          <!-- 使用通配符配置了Action名,method属性是一个表达式  -->
          <action name="*Pro" class="org.crazyit.struts2.action.LoginRegistAction"
                method="{1}">
                <!-- 下面定义了2个Result映射 -->
                <result name="error">/WEB-INF/content/error.jsp</result>
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
          <action name="*">
                <result>/WEB-INF/content/{1}.jsp</result>
          </action>
    </package>
</struts>

上面的<action name="*Pro" .../>元素不是定义了一个普通的Action,而是定义了一系列的逻辑Action——只要用户请求的URL是*Pro模式,都可以通过该Action类处理。配置该Action元素时,还指定了method属性(method属性用于指定处理用户请求的方法),但该method属性使用了一个表达式{1},该表达式的值就是name属性值中第一个*的值。例如,如果用户请求的 URL 为 loginPro,则调用 LoginRegistAction 类的 login 方法;如果用户请求的 URL 为registPro,则调用LoginRegistAction类的regist方法。

下面是本应用中LoginRegistAction类的代码。

程序清单:codes\03\3.5\wildcard\WEB-INF\src\org\crazyit\struts2\action\LoginRegistAction.java

public class LoginRegistAction extends ActionSupport
{
    // 封装用户请求参数的两个属性
    private String username;
    private String password;
    // 封装处理结果的tip属性
    private String tip;
    // username属性对应的setter和getter方法
    public String getUsername()
    {
          return username;
    }
    public void setUsername(String username)
    {
          this.username = username;
    }
    // password属性对应的getter和setter方法
    public String getPassword()
    {
          return password;
    }
    public void setPassword(String password)
    {
          this.password = password;
    }
    // tip属性对应的setter和getter方法
    public String getTip()
    {
          return tip;
    }
    public void setTip(String tip)
    {
          this.tip = tip;
    }
    // Action包含的注册逻辑
    public String regist() throws Exception
    {
          ActionContext.getContext()
                .getSession().put("user" , getUsername());
          setTip("恭喜您," + getUsername() + ",您已经注册成功!");
          return SUCCESS;
    }
    // Action包含的登录逻辑
    public String login() throws Exception
    {
          if (getUsername().equals("crazyit")
                && getPassword().equals("leegang") )
          {
                ActionContext.getContext()
                    .getSession().put("user",getUsername());
                setTip("欢迎," + getUsername() + ",您已经登录成功!");
                return SUCCESS;
          }
          else
          {
                return ERROR;
          }
    }
}

上面的Action类不再包含默认的execute方法,而是包含了regist和login两个方法,这两个方法与execute方法除了方法名不同外,其他完全相同。

同样对于图3.17所示的页面,我们修改JavaScript中regist()函数的代码为如下形式。

function regist()
{
    // 获取页面的第一个表单
    targetForm = document.forms[0];
    // 动态修改表单的action属性
    targetForm.action = "registPro";
    // 提交表单
    targetForm.submit();
}

在上面方法中看到,当浏览者单击“注册”按钮时,动态修改表单的action属性为registPro,该请求匹配了*Pro模式,将交给该Action处理;当registPro匹配*Pro模式时,*的值为regist,则调用regist方法来处理用户请求。

除此之外,表达式也可出现在<action .../>元素的class属性中,即Struts 2允许将一系列的Action类配置成一个<action .../>元素。

看下面的struts.xml配置文件片段。

程序清单:codes\03\3.5\wildcard2\WEB-INF\src\struts.xml

<?xml version="1.0" encoding="GBK" ?>
<!-- 指定Struts 2配置文件的DTD信息 -->
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <!-- Struts 2的Action必须放在指定的包空间下定义 -->
    <package name="struts2qs" extends="struts-default">
          <!-- 使用通配符配置了Action名,class属性是一个表达式  -->
          <action name="*Pro" class="org.crazyit.struts2.action.{1}Action">
                <!-- 下面定义了2个Result映射 -->
                <result name="error">/WEB-INF/content/error.jsp</result>
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
          <action name="*">
                <result>/WEB-INF/content/{1}.jsp</result>
          </action>
    </package>
</struts>

上面的<action .../>定义片段定义了一系列的Action,这一系列的Action名字应该匹配*Pro模式,没有指定method属性,即默认总是使用execute方法来处理用户请求。但class属性值使用了表达式,上面配置片段的含义是,如果有 URL 为 RegistPro 的请求,将可以匹配*Pro模式,交给该Action处理,其第一个*的值为Regist,该Regist传入class属性值,即该Action的处理类为RegistAction;如果有URL为LoginAction的请求,则处理类为LoginAction。因此,如果我们需要系统处理 RegistAction 和 LoginAction 两个请求,则必须提供 RegistAction 和LoginAction 两个处理类,这两个文件可以在 codes\03\3.5\wildcard2\WEB-INF\src\org\crazyit\struts2\action路径下找到。

当然,我们应该将图3.17所示页面代码中的regist函数改为如下形式。

function regist()
{
    // 获取页面的第一个表单
    targetForm = document.forms[0];
    // 动态修改表单的action属性
    targetForm.action = "RegistPro";
}

如果有需要,Struts 2允许在class属性和method属性中同时使用表达式。看如下配置片段:

<!-- 定义了一个action,同时在class属性和method属性中使用表达式 -->
<action name="*_*" method="{2}" class="actions.{1}">

上面的配置片段定义了一个模式为*_*的Action,即只要匹配该模式的请求,都可以被该Action处理。如果有URL为Book_save.action的请求,因为匹配了*_*模式,且第一个*的值为Book,第二个*的值为save,则意味着调用Book处理类的save方法来处理用户请求。

本节第 5 章将会介绍关于 Action 的输入校验。在对 Action 进行输入校验时,必须为该Action指定对应的校验文件。那么对于模式为*_*的Action,应该定义怎样的校验文件呢?

因为Struts 2默认的校验文件命名遵守如下规则:ActionName-validation.xml,即如果有类名为MyAction的Action类,则应该提供名为MyAction-validation.xml的校验文件。

但对于上面的<action .../>配置元素,class 属性值是一个表达式,这个表达式的值来自于前面action的name属性。例如,如果有URL为Book_save.action的请求,则该Action对应的处理类为Book,对应的校验文件名为Book-validation.xml。

即使对于class属性值固定的Action,同样可以为一个Action类指定多个校验文件。看如下的Action配置片段。

<!-- 配置了Action,指定了固定的class属性,而method属性使用表达式 -->
<action name="crud_*" class="lee.Crud" method="{1}">

在上面的配置片段中,指定了该Action的实现类为lee.Crud。该Action的name是一个模式字符串,则该Action将可以处理所有匹配crud_*的请求。

假设有URL为crud_input的请求,该请求匹配了crud_*模式,则该Action可以处理该请求。对于该请求,Struts 2将采用Crud_input-validation.xml校验文件来进行数据校验。

提示:

关于数据校验的详细介绍,请参阅本书第5章的内容。

实际上,Struts 2不仅允许在class属性、name属性中使用表达式,还允许在<action .../>元素的<result .../>子元素中使用表达式。例如,前面经常看到的配置:

<!-- 定义一个通用Action -->
<action name="*" >
    <!-- 使用表达式定义Result -->
    <result>/WEB-INF/content/{1}.jsp</result>
</action>

在上面的Action定义中,Action的名字是一个*,它可以匹配任意的Action,即所有的用户请求都可通过该 Action 来处理。因为没有为该 Action 指定 class 属性,即该 Action 使用ActionSupport来作为处理类,而且因为该ActionSupport类的execute方法返回success字符串,即该Action总是直接返回result中指定的JSP资源,JSP资源使用了表达式来生成资源名。上面 Action 定义的含义是,如果请求 a.action,则进入/WEB-INF/content/a.jsp 页面;如果请求b.action,则进入/WEB-INF/content/b.jsp页面……依此类推。

现在的问题是,当用户请求的URL同时匹配多个Action时,究竟由哪个Action来处理用户请求呢?

假设有URL为abcAction的请求,在struts.xml文件中配置了如下三个Action,它们的name属性值分别为:abcAction、*Action和*,则这个请求将被名为abcAction的Action处理。

如果有URL为defAction.action 的请求,struts.xml文件中同样配置了abcAction、*Action和*三个Action,defAction.actin的请求显然不会被name为abcAction的Action处理,但到底是被name为*Action,还是被name为*的Action来处理呢?

看如下的struts.xml文件配置片段。

程序清单:codes\03\3.5\matchSequence\WEB-INF\src\struts.xml

<?xml version="1.0" encoding="GBK"?>
<!-- 指定Struts 2配置文件的DTD信息 -->
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
    "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
    <package name="lee" extends="struts-default">
          <!-- 配置name为*的Action -->
          <action name="*" class="org.crazyit.struts2.action.FirstAction">
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
          <!-- 配置name为*Action的Action -->
          <action name="*Action" class="org.crazyit.struts2.action.TwoAction">
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
          <!-- 配置name为loginAction的Action -->
          <action name="loginAction" class="org.crazyit.struts2.action.LoginAction">
                <result name="error">/WEB-INF/content/error.jsp</result>
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
    </package>
</struts>

如果有URL为registAction的请求,则请求不是由上面配置文件中的第二个action来处理,而是由第一个action来处理的。

将上面的struts.xml文件修改成如下形式。

<?xml version="1.0" encoding="GBK"?>
<!-- 指定Struts 2配置文件的DTD信息 -->
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
    "http://struts.apache.org/dtds/struts-2.1.dtd">
<struts>
    <package name="lee" extends="struts-default">
          <!-- 配置name为*Action的Action -->
          <action name="*Action"lt@span b=1> class="org.crazyit.struts2.action.TwoAction">
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
          <!-- 配置name为*的Action -->
          <actionlt@span b=1> name="*" class="org.crazyit.struts2.action.FirstAction">
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
          <!-- 配置name为loginAction的Action -->
          <action name="loginAction" class="org.crazyit.struts2.action.LoginAction">
                <result name="error">/WEB-INF/content/error.jsp</result>
                <result name="success">/WEB-INF/content/welcome.jsp</result>
          </action>
    </package>
</struts>

如果有URL为RegistAction.action的请求,该请求将由name为*Action的Action来处理。

通过上面配置文件的对比,可以得出这样的规律:如果有URL为abcAction 的请求,且struts.xml文件中有名为abcAction的Action,则一定由该Action来处理用户请求;如果struts.xml文件中没有名为abcAction的Action,则搜寻name属性值匹配abcAction的Action,例如name为*Action或*,*Action并不会比*更优先匹配abcAction的请求,而是先找到哪个Action,就先由哪个Action来处理用户请求。

注意

因为除非请求的URL与Action的name属性绝对相同,否则将按先后顺序决定由哪个Action来处理用户请求。因此,我们应该将名为*的Action配置在最后,否则Struts 2将使用该Action来处理所有希望使用模式匹配的请求。