Struts2的Action(下)
5) 动态方法的调用
Struts1框架提供了一个DispatchAction,从而允许一个Action里面包含多个处理逻辑,比如说对于同一个表单,当用户通过不同的请求按钮提交表单的时候,应该使用Action的不同的方法来处理请求.
Struts2同样提供了这种处理多个请求的Action.可以采用DMI(Dynamic Method Invocation动态方法)调用来处理一个表单多种请求的情况,动态方法调用是指表单元素的action并不是直接等于某个Action的名字,而是以如下形式来制定Form的action属性.
<!- -action的属性为actionName!methodName的形式 -->
action=”ActionName!methodName.action”
<!- - 注册按钮是一个没有任何动作的按钮,但单机该按钮的时候会触发regist函数-- >
<input type=”button” value=”注册” onClick=”regist()”/>
单机”注册”按钮的时候将会激发regist函数,该函数的代码如下:
function regist(){
//获取JSP页面中一个表单元素
targetForm=document.forms[0];
//东该修改目标表单的action属性
targetForm.action=”Login!regist.action”;
//提交表单
targetForm.submit();
}
上面JavaScript代码的核心在于动态的修改表单元素的Action属性,修改之后action属性为Login!regist.action,其实质就是将该表单提交给loginAction的regist方法进行处理.
LoginAction的代码如下:
package com.supermos.app.Action;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ServletResponseAware;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
public class LoginAction implements Action,ServletResponseAware{
//需要访问的HttpServletResponse对象
private HttpServletResponse response;
private String username;
private String password;
private String tip;
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
public String getTip() {
return tip;
}
public void setTip(String tip) {
this.tip = tip;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//实现ServletResponseAware接口所必须要实现的一个方法
public void setServletResponse(HttpServletResponse response) {
//在该方法内就可以访问web应用对客户的响应对象
this.response = response;
}
public String regist(){
ActionContext.getContext().getSession().put("user", this.getUsername());
this.setTip("恭喜您"+this.getUsername()+",您注册成功了");
return this.SUCCESS;
}
public String execute(){
//获取ActionContext实例,通过该实例访问Servlet API
ActionContext ctx=ActionContext.getContext();
//获取ServletContext里面的counter属性
Integer counter=(Integer)ctx.getApplication().get("counter");
//如果counter的属性为空,就设置该counter的属性为1
if(counter==null){
counter=1;
}
//否则,将counter加1
else{
counter=counter+1;
}
//将增加1之后的counter的值设置为counter属性
ctx.getApplication().put("counter", counter);
//将登陆用的username属性设置为一个HttpSession属性
ctx.getSession().put("user",this.getUsername());
if("supermos".equalsIgnoreCase(this.getUsername())&&"ziwen".equalsIgnoreCase(this.getPassword())){
//直接设置HttpServletRequest属性,下面的代码的作用类似于设置HttpServletRequest属性
//request.setAttribute("tip","服务器提示,您已经成功登陆");
ctx.put("tip", "服务器提示,您已经成功登陆");
//创建一个user对象
Cookie c=new Cookie("user",this.getUsername());
//设置cookie对象的最大的生存时间
c.setMaxAge(60*60);
//使用HttpServletResponse来添加Cookie对象
response.addCookie(c);
return this.SUCCESS;
}else{
//直接设置HttpServletRequest属性
ctx.put("tip","服务器提示,您登陆失败,请检查您的用户名和密码");
return this.ERROR;
}
}
}
当浏览者点击”注册”按钮的时候,系统将提交给login Action的regist方法处理.通过这种方式,我们可以再一个Action中包含多个处理逻辑,并可以通过为表单元素指定不同的action属性来提交给Action的不同的方法.
对于使用动态方法调用的方法,比如说regist方法,该方法的方法声明与系统默认的execute方法的方法声明只有方法名字不相同,其他的比如方法的参数,返回值的类型都应该绝对的相同.
(使用动态方法调用前必须设置Struts2允许动态方法调用,开启系统的动态方法调用是通过设置struts.enable.DynamicMethodInvocation常量来完成的,设置该常量的值为true将开启动态方法调用,否者将关闭动态方法调用)
6) 为action元素指定method属性
对于一个表单中有含有两个不同的按钮,分别提交给不同的处理逻辑的情况下,Struts2还提供了一种另外的方法,即将一个Action处理类定义成多个逻辑Action.如果在配置<action…/>元素的时候,指定action的method属性,则可以让Action类调用指定方法,而不是execute方法来处理用户的请求.
比如说,我们可以有如下的配置片段:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="supermos" extends="struts-default">
<action name="login" class="com.supermos.app.Action.LoginAction">
<result name="success">/success.jsp</result>
<result name="error">/error.jsp</result>
<result name="input">/login.jsp</result>
</action>
<action name="regist" class="com.supermos.app.Action.LoginAction" method="regist">
<result name="success">/success.jsp</result>
<result name="error">/error.jsp</result>
<result name="input">/login.jsp</result>
</action>
</package>
</struts>
上面定义了Login和Regist两个逻辑Action,它们对应的处理类都是com.supermos.app.Action.LoginAction,Login和Regist两个Action虽然有相同的处理类,但是处理逻辑不一样----处理逻辑通过method方法来制定,其中名为login的Action对应的处理逻辑为默认的execute方法,而regist的Action对应的逻辑处理为指定的regist方法.
将一个Action处理类定义成两个Action之后,可以再修改JSP页面的JavaScript代码,修改regist函数的代码为如下的形式:
<script type="text/javascript">
function regist(){
var form=document.forms[0];
form.action=" regist.action";
form.submit();
}
</script>
通过这种方式,一样可以完成上面的效果,当浏览者点击”登录”按钮的时候,将提交给Action的登录逻辑处理,当浏览者单击”注册”按钮的时候,将提交给Action类的注册逻辑处理.
(这种将一个Action类映射成多个逻辑Action的方式,非常类似于Struts1的MappingDispachAction的处理方式,在Struts1中,将一个MappingDispachAction类可以定义成多个逻辑Action)
我们再次看到上面struts.xml文件中两个<action../>元素定义的时候,我们发现两个action定义的绝大部分是相同的,因此,这种方式相当的冗余,为了解决这个问题,Struts2还有另外一种形式的动态方法的调用,也就是使用通配符的形式.
7) 使用通配符
在配置<action../>元素的时候,需要指定name,class还有method属性,这三个属性都可以支持通配符,这种使用通配符的方式是另外的一种动态的方法的调用,当我们使用通配符定义Action的name属性的时候,就相当于一个元素Action定义多个逻辑Action.
看下面的struts.xml配置文件的代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="supermos" extends="struts-default">
<!-- 使用通配符配置了Action名,method属性是个动态值 -->
<action name="*Action" class="com.supermos.app.Action.LoginAction" method="{0}">
<!-- 定义了三个动态的result -->
<result name="success">/success.jsp</result>
<result name="error">/error.jsp</result>
<result name="input">/login.jsp</result>
</action>
</package>
</struts>
(代码中是name=”*Action”不要写成了”*Action.action”…..2009年09月02日)
上面的<action name=”*.Action” …/>不是定义了一个普通的Action,而是定义了一系列的逻辑Action—只要用户请求的URL是*Action.action的模式,都可以通过该Action类来进行处理.配置该action元素的时候,还制定method属性(method属性用于指定处理用户请求的方法),但该method属性使用了一个表达式{0},该表达式的值就是name属性值中第一个*的值.例如,如果用户请求的URL为loginAction.action,则调用com.supermos.app.Action.LoginAction的login方法,如果请求URL为registAction.action,这调用的是com.supermos.app.Action.LoginAction的regist方法.
下面是本应用的LoginAction类的代码:
package com.supermos.app.Action;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ServletResponseAware;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
public class LoginAction implements Action,ServletResponseAware{
//需要访问的HttpServletResponse对象
private HttpServletResponse response;
private String username;
private String password;
private String tip;
public HttpServletResponse getResponse() {
return response;
}
public void setResponse(HttpServletResponse response) {
this.response = response;
}
public String getTip() {
return tip;
}
public void setTip(String tip) {
this.tip = tip;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//实现ServletResponseAware接口所必须要实现的一个方法
public void setServletResponse(HttpServletResponse response) {
//在该方法内就可以访问web应用对客户的响应对象
this.response = response;
}
public String regist(){
ActionContext.getContext().getSession().put("user", this.getUsername());
this.setTip("恭喜您"+this.getUsername()+",您注册成功了");
return this.SUCCESS;
}
public String login(){
//获取ActionContext实例,通过该实例访问Servlet API
ActionContext ctx=ActionContext.getContext();
//获取ServletContext里面的counter属性
Integer counter=(Integer)ctx.getApplication().get("counter");
//如果counter的属性为空,就设置该counter的属性为1
if(counter==null){
counter=1;
}
//否则,将counter加1
else{
counter=counter+1;
}
//将增加1之后的counter的值设置为counter属性
ctx.getApplication().put("counter", counter);
//将登陆用的username属性设置为一个HttpSession属性
ctx.getSession().put("user",this.getUsername());
if("supermos".equalsIgnoreCase(this.getUsername())&&"ziwen".equalsIgnoreCase(this.getPassword())){
//直接设置HttpServletRequest属性,下面的代码的作用类似于设置HttpServletRequest属性
//request.setAttribute("tip","服务器提示,您已经成功登陆");
ctx.put("tip", "服务器提示,您已经成功登陆");
//创建一个user对象
Cookie c=new Cookie("user",this.getUsername());
//设置cookie对象的最大的生存时间
c.setMaxAge(60*60);
//使用HttpServletResponse来添加Cookie对象
response.addCookie(c);
return this.SUCCESS;
}else{
//直接设置HttpServletRequest属性
ctx.put("tip","服务器提示,您登陆失败,请检查您的用户名和密码");
return this.ERROR;
}
}
}
上面的Action类不再包含默认的execute方法,而是包含了regist和login两个方法,这两个方法与execute方法除了方法名字不相同之外,其他的完全相同.
我们修改JavaScript中regist函数的代码为如下的形式:
<script type="text/javascript">
function regist(){
//获取页面中的第一个表单元素
var form=document.forms[0];
//动态修改表单的action属性
form.action="registAction.action";
//提交表单
form.submit();
}
</script>
在上面的方法中看到,当浏览者单击”注册”按钮的时候,动态修改表单的action属性为registAction.action,该请求匹配了*Action的形式,将交给Action处理:registAction匹配*Action模式的时候.*的值为regist,则调用regist方法来处理用户的请求.
除此之外,表达式也可以出现在<action../>元素的class属性当中,也就是Struts2允许将一系列的Action配置成一个<action../>元素.
看下面的struts.xml配置文件的片段:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="supermos" extends="struts-default">
<action name="*Action" class="com.supermos.app.Action.{1}Action">
<result name="input">/login.jsp</result>
<result name="error">/error.jsp</result>
<result name="success">/welcome.jsp</result>
</action>
</package>
</struts>
上面的<action…/>片段定义了一系列的Action,这系列的Action名字应该匹配*Action的模式,没有指定method属性,也就是说,总是使用execute方法来处理用户的请求,但是class属性值使用了表达式,上面的配置片段的含义是,如果有URL为RegistAction.action请求,就可以匹配*Action模式,将交给该Action处理,其第一个*的值为Regist,将该Regist传入class属性值,也就是该Action的处理类为com.supermos.app.Action.RegistAction.
如果有需要,Struts2允许在class属性和method属性中同时使用表达式,看一下下面饿一个配置片段.
<!-- 定义了一个action,同时在class属性和method属性中使用表达式 -->
<action name=”*_*” method=”{2}” class=”action.{1}”>
上面的定义片段定义了一个模式为*_*的Action,也就是说,只要匹配该模式的请求,都可以被该Action处理.如果有URL为Book_save.action的请求,因为匹配了*_*的模式,并且第一个*的值为Book,第二个*的值为save,就意味着可以调用Book处理类的save方法来处理用户的请求.
实际上Struts2不仅允许在class属性,name属性中使用表达式,还可以在<action../>元素的result子元素中使用表达式,下面提供了一个通用Action,该Action可以配置成如下的形式:
<!-- 定义一个通用的Action -->
<action name="*">
<!-- 使用表达式定义Result -->
<result>/{1}.jsp</result>
</action>
在上面的Action的定义中,Action的名字是一个*,它可以匹配任意的Action.所有的用户请求都可以通过该Action来进行处理,因为没有为该Action指定class属性,也就是说该Action使用ActionSupport来作为处理类,而且因为该ActionSupport类的execute方法返回success字符串,即该Action总是直接返回result中指定的JSP资源,JSP资源使用了表达式来生成资源名,上面的Action定义的含义是:
如果请求a.action,则进入a.jsp,如果请求b.action则进入b.jsp页面,以此类推.
现在的问题是,当用户请求的URL同时匹配多个Action的时候,究竟由哪个Action来进行处理?
不如说,现在有URL为abcAction.action的请求,在Struts.xml文件中配置了如下的三个Action,他们的Action name的值分别为abcAction,*Action还有*,则这个请求名将被abcAction的Action进行处理.
如果有URL为defAction.action的请求,struts.xml文件中同样配置了abcAction,*Action和*的一个Action,defAction.action的请求显然不会被name为abcAction的Action处理,到底是被name为*Action处理还是被*的Action来处理呢?
如果有URL为abcAction.action的请求,如果struts.xml文件中没有名为abcAction的Action,则一定由该Action来处理用户请求:如果struts.xml文件中没有名为abcAction的Action,则搜寻name属性值匹配abcAction的Action,例如name为*Action或者*,*Action并不会比*更加优先匹配abcAction的请求,而是先找到那个Action,就先有那个Action来处理用户的请求.
(因为除非请求的URL与Action name属性绝对相同,否则将按照先后顺序来决定哪个Action来处理用户的请求,因此,我们在写配置文件的时候,要尽量将*的Action配置到最后,否者Struts2将使用该Action来处理所有希望使用模式匹配的请求)
8) 默认的Action
在某些情况下,用户请求非常简单,不需要系统过多的处理,或者这些请求只是一个简单的转发作用.
(提示: 对于使用Struts2框架的应用而言,尽量不要让超级链接直接连接到某个视图资源,因为这种方式增加了额外的风险,推荐将所有的请求都发送给Struts2框架,让该框架来处理用户的请求,哪怕是只是简单的超级链接).
对于只是简单的超级链接的请求,可以通过定义name为*的Action(该Action应该放在最后定义)实现,除此之外,Struts2还允许在容器中定义一个默认的Action,当用户的请求的URL在容器中找不到对应的Action的时候,系统将使用默认的Action来处理用户的请求.
配置默认的Action通过<def-action-ref../>元素完成,每个<default-action-ref../>元素配置一个默认Action下面的struts.xml配置片段配置了一个默认的Action.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="supermos" extends="struts-default">
<default-action-ref name="simpleViewResultAction"/>
<action name="simpleViewResultAction" class="com.supermos.app.Action.LoginAction" method="{1}">
<result name="success">/success.jsp</result>
<!-- 此处省略n行 -->
</action>
</package>
</struts>
从上面的配置文件就可以看到,配置默认的Action只需要配置<default-action-ref../>元素就可以了,配置该元素的时候需要指定一个name属性,该name属性指向容器中另外一个有效的Action,该Action将成为该容器中默认的Action.