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来处理所有希望使用模式匹配的请求。