实现Action
Action是struts2应用的核心,开发中需要大量的Action类,并在struts.xml中配置Action。Action中包含了对用户请求的处理逻辑,Action类也被称为业务控制逻辑器。
struts2采用低侵入式设计,它不要求Action类继承任何的struts的基类或者实现任何struts接口。struts2的Action类是普通的POJO类(通常应该带一个无参的方法),从而有很好的代码复用性。
struts2通常直接使用Action来封装HTTP参数,因此,Action类还应该包含于请求参数对应的实例变量,并且为这些实例变量提供相应的setter和getter方法。
比如用户请求包含productName和productDesc两个请求参数,Action类应该提供productName和productDesc两个实例变量来封装用户请求参数,并且为productName和productDesc提供对应的setter和getter方法。下面是处理该请求的Action类的代码片段:
//处理用户请求的Action类,只是一个POJO,无需继承任何基类,或实现任何接口
public class Product {
//提供实例变量来封装HTTP请求参数
private Integer productId;
private String productName;
private String productDesc;
private double productPrice;
//相应的setter和getter方法
public Integer getProductId() {
return productId;
}
public void setProductId(Integer productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getProductDesc() {
return productDesc;
}
public void setProductDesc(String productDesc) {
this.productDesc = productDesc;
}
public double getProductPrice() {
return productPrice;
}
public void setProductPrice(double productPrice) {
this.productPrice = productPrice;
}
//Action类默认处理用户请求的方法:execute方法
public String execute(){
...
//返回处理结果字符串
return resultStr;
}
虽然Action需要处理的请求包含productName和productDesc等HTTP请求参数,Action类也可以不包含它们对应的实例变量。因为系统是通过对应的setter和getter方法来处理请求参数的,而不是通过实例变量来处理请求参数的。即Action类里包含对应实例变量与否不重要,重要的是需要包含void setProductName(String productName)和String getProductName()方法。
Action类里的实例变量不仅可以用来封装请求参数,还可以用来封装处理结果。如果希望将服务器提示的“注册成功”或其他信息在下一个页面输出,就可以在Action类中增加一个tip实例变量,并为之提供对应setter和getter方法,
//封装服务提示的tip实例变量
private String tip;
//tip对应的setter和getter方法
public String getTip(){
return tip;
}
pubic void setTip(String tip){
this.tip = tip;
}
//Action包含的注册控制逻辑
public String register() throws Exception{
ActionContext.getContext().getSession().put("user",getUsername());
setTip("欢迎你!" + getUsername() + "你已经注册成功!");
return SUCCESS;
}
可以在下一个页面中使用struts2标签来输出该实例变量的值。加载JSP页面中输出tip实例变量值得代码片段如下:
<s:property value="tip"/>
系统不严格区分Action里哪个实例变量用于封装请求参数,哪个实例变量用于封装处理结果。如果用户的HTTP请求包含名为tip的请求参数,则系统会调用void setTip(String tip)方法,通过这种方式,名为tip的请求参数就可以传给Action实例;如果Action类里没有包含对应的方法,则名为tip的请求参数将无法传入该Action。
在JSP页面中输出Action的实例变量值时,也不区分该实例变量是用于封装请你参数还是处理结果。因此,struts2标签既可以输出Action的处理结果也可以输出HTTP请求参数值。
Action接口和Action基类
为规范Action类开发,struts2提供了一个Action接口,给接口定义了struts2的Action处理类应该实现的规范:
public interface Action {
//定义Action接口里包含的一些结果字符串
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
//定义处理用户请求的execute方法
public String execute() throws Exception;
}
Action接口里定义了一个execute方法,该接口的规范定义了Action类应该包含一个execute方法,该方法返回一个字符串。该接口还定义了5个字符串常量,用于统一execute方法的返回值。一般Action类处理用户请求后,有人喜欢返回welcome字符串,有人喜欢返回success字符窜… …这样不利于统一关联,故Action接口定义了这5个字符串常量:ERROR、
INPUT、LOGIN和SUCCESS,分别代表特定含义。但是,如果开发者希望用自己的字符串作为逻辑视图名,还是可以被允许的,但这并不利用后期维护。
struts2还为Action接口提供一个实现类ActionSupport,其包含相关方法等见图
AcitonSupport是个默认的Action实现类,该类里有很多默认方法,包括获取国际化信息的方法、数据校验的方法、默认的处理用户请求的方法等。它是struts2默认的Action处理类,如果让开发者的Action处理类继承ActionSupport类,会大大简化Action的开发。
Action访问Servlet API
struts2的Action没有与任何Servlet API耦合,从而能轻松地测试该Action。但是Web应用的控制器经常都是要求访问Servlet API的,例如跟踪HTTPSession状态等,通常Web应用要访问的Servlet API有HttpServletRequest、HttpSession、ServletContext等,这三个接口分别代表JSP内置对象中的request、session和application。
与Servlet API解耦的访问方式 :
为了避免与 Servlet API 耦合在一起, 方便 Action 做单元测试, Struts2 对 HttpServletRequest, HttpSession 和 ServletContext 进行了封装, 构造了 3 个 Map 对象来替代这 3 个对象, 在 Action 中可以直接使用 HttpServletRequest, HttpServletSession, ServletContext 对应的 Map 对象来保存和读取数据.
struts2提供了ActionContext类,ActionContext 是 Action 执行的上下文对象, 在 ActionContext 中保存了 Action 执行所需要的所有对象, 包括 parameters, request, session, application 等.
获取 HttpSession 对应的 Map 对象:
public Map getSession()
获取 ServletContext 对应的 Map 对象:
public Map getApplication()
获取请求参数对应的 Map 对象:
public Map getParameters()
获取 HttpServletRequest 对应的 Map 对象:
public Object get(Object key): ActionContext 类中没有提供类似 getRequest() 这样的方法来获取 HttpServletRequest 对应的 Map 对象. 要得到 HttpServletRequest 对应的 Map 对象, 可以通过为 get() 方法传递 “request” 参数实现。
完整方法如下:
与 Servlet 耦合的访问方式:
用ActionContext访问Servlet API不是直接获得Servlet API的实例。为在Action中直接访问Servlet API,struts2提供如下接口:
|>ServletContextAware:实现该接口的Action可以直接访问Web应用的ServletContext实例。
|>ServletRequestAware:实现该接口的Action可以直接访问用户请求的HttpServletRquest实例。
|>ServletResponseAwre:实现该接口的Action可以直接访问服务器响应的HttpServletResponse实例。
另外,struts2还提供一个ServletActionContext工具类直接访问Servlet API,
借助ServletActionContext类可以在Action中访问Serlevt API,并可以避免Action类需要实现XxxAware接口,但该Action依然与Servlet API直接耦合,测试时需要有 Servlet 容器, 不便于对 Action 的单元测试,一样不利于高层次的解耦。
Action的动态方法调用
这篇写Action相关,本来应该还涉及到Action的基本配置,但前面一篇讲开发流程时基本介绍到,这里不再赘言,直接看Action的动态方法调用。
很多时候要求一个Action内包含多个控制处理逻辑。比如对同一个表单,用户可能要通过登录或注册两个不同提交按钮来提交同一个表单系统需要使用Action不同方法来处理用户请求,这就需要让同一个Action里包含多个控制处理逻辑。
”登录”按钮使用登录逻辑处理请求,“注册”按钮使用注册逻辑处理请求。可采用DMI(Dynamic Method Invocaton,动态方法调用)来处理请求。动态方法调用中表单元素的action并不是直接等于某个Action的名字,而是按如下形式来指定表单的action属性。
fun(){ <!--action属性为actionName!methodName的形式-->
target.action="action!methodName";
}
如我们可以将JSP页面“注册”按钮的代码写成:
<!--注册按钮没有任何动作,但单击该按钮时触发regist函数-->
<input type"submit" value="注册" onClick="regist();">
regist函数JavaScript代码:
function regist(){
//获取页面的第一个表单
targetForm = document.forms[0];
targetForm.action = "login!regist";
}
最后一行代码就是说将该表单提交给login Action的regist方法处理。LoginRegistAction类的代码如下:
public class LoginRegistAction extends ActionSupport{
//封装用户请求参数的两个成员变量
private String username;
private String password;
//封装处理结果的tip成员变量
private String tip;
//省略setter和getter方法
...
//Action包含的注册控制逻辑
public String register() throws Exception{
ActionContext.getContext().getSession().put("user",getUsername());
setTip("欢迎你!" + getUsername() + "你已经注册成功!");
return SUCCESS;
}
//Action默认包含的控制逻辑
public String execute() throws Exception{
if(getUsername().equals("Jujiu")&&getPassword().equals("1357afy"))
{
ActionContext.getContext().getSession().put("user",getUsername());
setTip("欢迎你!" + getUsername()+"你已经注册成功!");
return SUCCESS;
}
}
}
默认调用execute方法,当用户单击登录,系统将表单提交给LoginActionAction默认方法,当用户单击“注册”按钮,该表单的action会被修改为:login!regist,系统将提交给login Action(即LoginRegistAction)的regist方法处理。这种方式可以使一个Action包含多个逻辑处理,并通过为表单元素指定不同action属性来提交给action的不同方法。
使用动态方法调用,其中的方法如regist方法的声明和系统默认的execute方法除了方法名不一样外,其他如形参列表、返回值类型都应完全相同。
使用动态方法调用前需要设置struts2运行动态方法调用,即设置struts.enable.DynamicMethodInvocation常量的值为true。DMI方式存在安全缺陷。不建议常用。
method属性指定方法和通配符使用
可以使用中的method属性将一个Action类配置成多个逻辑Action,让Action调用指定方法。
<struts>
<package name="helloWorld" extends="struts-default">
<action name="login" class="com.afy.LoginRegist">
<result name="details">
/WEB-INF/pages/welcome.jsp
</result>
</action>
<action name="regist" class="com.afy.LoginRegist" method="regist">
<result name="details">
/WEB-INF/pages/welcome.jsp
</result>
</action>
</package>
</struts>
这里定义login和regist两个逻辑Action,处理类都是LoginRegist,处理逻辑由method方法指定,login的Action对应的处理逻辑是默认的execute方法,而regist的Action对应的处理逻辑是指定的regist方法。相应的JavaScript代码改为:
function regist(){
//获取页面的第一个表单
targetForm = document.forms[0];
targetForm.action = "regist";
}
我们发现多个< action…/>中的定义大部分相同,造成冗余,此时可以考虑使用通配符方式。在配置< action…/>,指定name属性时使用模式字符串(用“*”代表一个或多个任意字符),可以在class、method属性及< result…/>子元素中使用表达式{N}的形式代替前面第N个星号所匹配的子串。
<struts>
<package name="helloWorld" extends="struts-default">
<action name="*Action" class="com.afy.LoginRegist" method=“{1}”>
<result name="details">
/WEB-INF/pages/welcome.jsp
</result>
</action>
</package>
</struts>