
3.5 Struts 2的控制层及Action设计与配置
开发基于Struts 2的Web应用程序时,Action是程序的核心。开发人员需要根据业务逻辑实现特定的Action类,并在struts.xml文件中配置Action。Action类中包含了对用户请求的处理逻辑,因此,也把Action称为Action业务控制器。
一般来说,每个Action类都有一个或多个方法来实现用户请求的处理,其中的每个方法会返回一个String类型的处理结果,该String值用于决定需要跳转(转向或重定向)到哪个视图或者另一个Action。
对于一个Action,需要传入值和传出值,即由请求传值给Action, Action处理加工后传值给视图(或另一个Action)。
本节主要介绍Action类的设计方法及Action的配置方法,以及Action传值方法,另外,还将介绍Action访问Web容器中的Web对象(request、response、session)的方法(Servlet API)。
3.5.1 Action类的实现与传值
Struts 2对Action的实现给出了不同的方法,实现方法不同,其接受参数(由提交信息页面提交给Action容器)的方式也不同,本小节分4种方式来讨论Action类的创建及接受参数的问题。
● 普通的Java类作为Action类与属性驱动传参。
● 继承ActionSupport实现Action与属性驱动传参。
● 领域对象属性驱动的Action类设计与属性驱动传参。
● 模型驱动(Model-Driven)的Action类与模型驱动传参。
无论采用上面4种方法的哪一种方式,设计Action类都必须满足以下条件:
1)Action类,每个属性都有sette/getterr方法,必须具有无参的构造方法。
2)该类除所必需的属性外,通常有一个execute()方法(或自定义方法),该方法无任何参数且返回字符串类型的值。
3)JSP页面与Action参数传递:JSP页面中使用的输入域属性(name属性值、EL表达式中的属性名)必须与Action类定义中的属性名称相同。
1.普通的Java类作为Action类与属性驱动传参
【例3-3】采用“普通的Java类作为Action类与属性驱动传参”方式,实现Action的设计与配置,实现【例3-1】中所要求的功能。
【分析】采用一般的Java类设计该Action类(SumAction.java),其中,属性x、y分别为加数和被加数,而sum用于存放计算结果;其方法为CalculateSum()计算和值,当和值为非负数时,返回“+”,否则返回“-”。
【实现】重新设计Action,其代码如下,注意其中的注释并与【例3-1】中代码对比。
package com.action; public class SumAction { private double x; //加数,页面接受该参数值 private double y; //被加数,页面接受该参数值 private double sum; //和值,将和值传出给其他视图或Action public String CalculateSum(){ //完成计算功能并返回字符串(页面逻辑值)的方法 String forward="-"; sum=x+y; //求和并存储于属性sum中,可以在其他视图或Action中访问sum if(sum>=0){ forward="+"; } return forward; } public SumAction() {}//无参构造方法 public SumAction(double x, double y) {//带参构造方法,但只有两个参数x、y this.x = x; this.y = y; this.sum =x+y; } //省略了各属性的setter/getter方法 }
【修改【例3-1】中的配置及其页面】
由于Action的实现修改了,其相应的配置文件及提交信息的页面和展示信息的页面都要进行相应的修改,其修改后的新内容如下。
(1)修改struts.xml配置文件,配置Action

(2)修改JSP页面
修改提交数据页面:input.jsp,代码如下。

修改positive.jsp和negative.jsp代码中的代码(修改其中的EL表达式如下)。
代数和为非负整数,其和值为:${x}+${y}=${sum}>
当运行程序时,其运行结果与【例3-1】完全一样。
2.继承ActionSupport实现Action与属性驱动传参
为了能够开发出更加规范的Action类,Struts 2提供了Action接口,并且为Action接口提供一个实现类ActionSupport。
(1)Action接口
Action接口定义了Struts 2的Action类中应该使用的规范,给出有关的字符串常量和一个方法。Struts 2类库中的Action接口(Action.java)的代码如下。
public interface 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"; public String execute()throws Exception; //声明方法 }
(2)Action接口的实现类ActionSupport
Struts 2为Action接口提供了一个实现类ActionSupport,其中的3个主要方法如下。
● void addFieldError(String fieldname, String errorMessage):添加字段验证的出错信息。
● String exeute():请求时执行的方法,需要重载。
● void validate():用于输入验证的方法。
在编写业务Action类时,建议继承ActionSupport实现Action类,并重写exeute()方法,或者添加新方法。对于addFieldError()和validate(),将在第4章给出详细介绍。
【例3-4】采用继承类ActionSupport的方式,重新设计Action。完成【例3-1】所要求的功能。假设要设计的Action为SumActionSupport.java,其代码如下。

修改struts.xml配置文件,配置Action。

方法execute()是Action默认方法,可以省略方法配置属性,简化为如下代码。
<action name="opadd"class="com.action.SumActionSupport">
其他组件都不需要修改。
3.领域对象的Action类设计与属性驱动传参
在实际应用中,根据需要,将各类信息设计成有关的实体类,在【例3-1】和【例3-2】中,都是首先设计了模型类,然后再设计Action,且Action中的一个属性是某实体类的对象属性。例如,【例3-1】中设计的实体类Add.java和Action类AddAction.java。
将这种设计Action的方法称为“领域对象属性驱动的Action”设计方法。在实际应用系统中采用较多的是这种方法。对于【例3-2】用户的登录与注册,也是采用这种方法设计的。
4.模型驱动(Model-Driven)的Action类与模型驱动传参
模型驱动是Struts 2独有的一种接收用户输入的机制。采用“模型驱动”设计Action类,必须实现ModelDriver接口,必须实现getMode()方法,该方法把Action及对应的Model实例(Action中声明的属性,该属性一定要实例化)关联。
【例3-5】采用模型驱动重新设计Action,实现【例3-1】所要求的功能。
采用“模型驱动设计Action”,必须先设计模型类,在【例3-1】中,已经设计了模型类Add,在此基础上设计Action类。这里也继承ActionSupport。
1)设计Action:要设计的Action为SumActionModelDriven.java,其代码如下。

2)修改struts.xml配置文件,配置Action。

3)修改JSP页面。
修改提交数据页面:input.jsp代码。

修改positive.jsp和negative.jsp代码中的代码(都修改为如下)。

4)当运行程序时,其运行结果与【例3-1】完全一样。
3.5.2 基于XML配置文件的Action配置与访问
在3.4.3节中已经较详细地介绍了struts.xml中常用的配置信息及配置方法,其中Action的配置最为重要,Action的配置方法不同,调用(访问)Action的方法也不同。在本小节中,进一步强化Action的配置,以及Action的调用方法。
1.Action配置
Struts 2中Action类的配置能够让Struts 2知道Action的存在,并可以通过调用该Action来处理用户请求。Struts 2使用包来组织和管理Action。
在一个Struts 2配置文件中,可以配置多个包,而每个包内可以再配置多个Action。配置格式及配置方法在前面已经介绍过,具体内容见3.4.3节。
2.如何访问Action
对所设计的Action,通过“包配置”和“Action配置”后,实际上就制定了访问使用Action的方式。
访问Struts 2中Action的URL由两部分组成:“包的命名空间”+“Action的名称”。
在图3-9中,其访问地址形成过程(注意图3-10中两种访问Action的差异)如下。

图3-9 访问Action的URL形成策略

图3-10 【例3-6】的提交页面
a) 提交页面b) 显示运行结果页面
1)对于配置的包tt1,其namespac="/test1",而Action的name="xyz",所以,形成的地址是:"/test1"与"xyz"的链接(注意,两者之间自动添加“/”),即地址为:"/test/xyz.actin"。
2)同样,对于配置包tt2,以及其该包下的Action配置,形成的访问地址为:"/test2/xyz/abcd.action"。
重要提示:
1)由于基于Struts 2开发Web应用程序,其实有两个容器(Action容器和Servlet容器),两个容器管理资源的相对根目录是不同的。
2)Action容器根目录:服务器的IP+:“端口号”+工程部署后根目录(一般为工程名称)。
3)Servlet容器根目录:服务器的IP+:“端口号”。
4)在Action容器内访问资源的通用格式(包括在Action中配置的JSP页面):
/按配置Action规范形成的访问Action的地址
注意:这里的首符号“/”必须有,表示从Action容器根目录开始寻找访问资源,若不是从Action容器根目录开始寻找,则最好给出要访问资源的绝对路径。
5)“超链接”操作是在“Servlet容器”内进行的,所以,在基于Struts 2设计的Web程序中,对于超链接,最好采用绝对路径访问资源。
3.5.3 多方法的Action设计与配置访问
前面所定义的Action都是一个方法配置一个Action。在实际的应用中,可以用一个Action处理多个业务请求,并在struts.xml指定业务处理所采用的方法。
【例3-6】对于【例3-1】,只实现了两个实数的求和运算。在本例中,要求修改该程序,完成两个数的四则运算(加、减、乘、除),提交页面如图3-10a所示,单击“求和”按钮,将完成求和运算并显示如图3-10b的结果,对于其他3个提交按钮类似。
【分析】在提交页面中有4个提交按钮,分别完成不同的计算功能,4种运算在一个Action中定义4种不同的方法来实现,这些方法的格式和execute()方法一样。本案例采用“领域对象的Action类设计与属性驱动传参”方式实现。
【实现】
1)首先设计一个领域类:Calculate.java,其代码如下。
package com.model; public class Calculate { private double x; // 第1个操作数,从提交页面获取信息的属性 private double y; // 第2个操作数,从提交页面获取信息的属性 //省略了各属性的setter/getter方法 public double add(){ return x+y; }// 求和方法 public double sub(){return x-y; }// 求差方法 public double mul(){return x*y; }// 求积方法 public double div(){return x/y; }// 求商方法,这里假设y不会出现0的情况 }
2)设计Action:CalculateAction.java,其代码如下。
package com.action; //省略了import; public class CalculateAction extends ActionSupport { private Calculate data; // 对象属性 private double value; // 用于存放计算结果,结果传给显示页面 private String msg; // 用于存放计算信息,结果传给显示页面 //省略了各属性的setter/getter方法 public String add()throws Exception{// 求和方法 value = data.add(); msg = "你选择的是求和运算!"; return "show"; } public String sub()throws Exception{// 求差方法 value = data.sub(); msg = "你选择的是求差运算!"; return "show"; } public String mul()throws Exception{// 求积方法 value = data.mul(); msg = "你选择的是求积运算!"; return "show"; } public String div()throws Exception{// 求商方法 value = data.div(); msg = "你选择的是求商运算!"; return "show"; } }
3)设计提交数据页面:input.jsp,其代码如下。
<head> <title>提交数据页面,并根据不同的按钮选择不同的业务处理</title> <script type="text/javascript"> function sub(){ document.aaa.action="sub"; } //动态修改表单的Action属性 function mul(){ document.aaa.action="mul"; } //动态修改表单的Action属性 function div(){ document.aaa.action="div"; } //动态修改表单的Action属性 </script> </head> <body> <form action="add" method="post" name="aaa"> 请输入两个整数:<br><br> 第1个运算数:<input name="data.x"/><br><br> 第2个运算数:<input name="data.y"/><br><br> <input type="submit" value="求和"/> <input type="submit" value="求差" onclick="sub()"/> <input type="submit" value="求积" onclick="mul()"/> <input type="submit" value="求商" onclick="div()"/> </form> </body>
4)设置显示运行结果的页面:show.jsp,其主要代码如下。
<body> ${msg} <br>第1个数为:${data.x} <br>第2个数为:${data.y} <br>运算结果为:${value} </body>
对于【例3-6】,在一个Action中,定义4个(多个)方法,如何配置这种情况下的Action呢?这种Action的配置及调用有以下3种方式。
● 为Action配置method属性。
● 动态方法调用。
● 使用通配符映射方式。
1.为Action配置method属性
Struts 2中为Action配置method属性值,需要在struts.xml中配置Action中的每个方法,而且每个Action配置中都要指定method属性。
对于【例3-6】,在struts.xml中配置Action,配置内容如下。
<package name="default" namespace="/" extends="struts-default"> <action name="add" class="com.action.CalculateAction" method="add"> <result name="show">/show.jsp</result> </action> <action name="sub" class="com.action.CalculateAction" method="sub"> <result name="show">/show.jsp</result> </action> <action name="mul" class="com.action.CalculateAction" method="mul"> <result name="show">/show.jsp</result> </action> <action name="div" class="com.action.CalculateAction" method="div"> <result name="show">/show.jsp</result> </action> </package>
2.动态方法调用
动态方法调用Action,其调用格式如下。
所配置的Action访问路径!方法名称
注意:在默认情况下,Struts 2的动态方法调用处于禁用状态,若要使用动态方法调用,需要在配置文件内配置允许动态调用信息,注意下面标注的说明。
在struts.xml中只需配置该Action,而不必配置每个方法,配置格式如下。

对于【例3-6】,在struts.xml中配置Action,配置内容可修改如下。

对于【例3-6】,还需要修改提交数据页面,修改的代码如下。

3.使用通配符映射方式
在struts.xml文件中配置Action元素时,它的name属性支持通配符。当使用通配符时,相当于用一个元素Action定义了多个逻辑Action。配置格式如下。

其中,用户请求的URL的模式是“Action名称_*”;同时,method属性值是一个表达式{1},表示它的值为name属性值中第一个“*”的值。
对于【例3-6】,在struts.xml中配置Action,配置内容如下。

同时,需要对【例3-6】修改提交数据页面input.jsp,修改的代码如下。

思考:对比3种配置方式,各自的优点和缺点是什么?
3.5.4 Action访问Web资源
在Struts 2中,Action类和Web对象之间没有直接关系,但是Action作为业务逻辑控制器,经常要访问Web资源。例如,在Action中要获取Web容器中的request和session对象中的信息。Struts 2提供了ActionContext类与ServletActionContext类用于Action访问Web资源,并且ServletActionContext类直接继承了ActionContext类。
在Action中访问Web对象有4种方式。
● 通过ServletActionContext直接访问Web对象——Servlet依赖容器方式。
● 通过ActionContext访问——Map依赖容器方式。
● 通过IoC访问Servlet对象——Map IoC方式。
● 通过IoC访问Servlet对象——Servlet IoC方式。
下面通过对【例3-3】(基于3个属性:x、y、sum设计的Action)进行修改,分别给出这4种访问方式的实现方法。
【例3-7】修改【例3-3】,将提交的加数x保存到request中,被加数y保存到session中,而和值sum保存在application中,并分别从request、session和application中获取数据并显示出来。
【分析】对于该题目,需要在Action中访问Web对象,并实现数据的保存,所以,只需要修改【例3-3】的Action,另外,需要修改显示页面(positive.jsp和negative.jsp)。
【实现】由于有4种方式通过Action访问Web对象,因此可以有4种实现方式。
1.利用ServletActionContex访问Web对象——Servlet依赖容器方式
Struts 2框架提供org.apache.struts2.ServletActionContext辅助类来获得Web对象。
HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); ServletContext application = ServletActionContext.getServletContext();
而HttpSession session的获取需要两步。
HttpServletRequest request = ServletActionContext.getRequest(); HttpSession session = request.getSession();
【实现方式1】
1)对于【例3-3】,重新设计Action:SumAction.java,其代码如下。

2)提交数据页面input.jsp和struts.xml不需要修改。
3)修改显示页面positive.jsp,其代码如下。
<body> 代数和为非负整数:${sum}<br> 加数:${request.x2}><br> 被加数:${session.y2}><br> 和值:&{application.sum2}><br> </body>
4)对于negative.jsp的修改,与positive.jsp类似,读者自己给出。
2.通过ActionContext访问——Map依赖容器方式(与Servlet API解耦访问方式)
Struts 2对HttpServletRequest、HttpSession和ServletContext进行了封装,构造了3个Map对象,在Action中直接使用HttpServletRequest、HttpServletSession和ServletContext对应的Map对象来保存和读取数据。ActionContext类中的常用方法如下。
● public static ActionContext getContext():获得ActionContext对象。
● public Object get(Object key):在ActionContext中查找key的值。
● public void put(Object key, Object value):向当前ActionContext存入值。
● public void setApplication(Map application):设置application的值。
● public void setSession(Map session):设置session的值。
● public Map getParameters():从请求对象中获取请求参数。
● public Map getApplication():获取ServletContext中保存的Attribute。
● public Map getSession():获取HttpSession中保存的Attribute。
若要实现Web的访问,首先需要获取ActionContext对象,然后利用该对象获取request、session和application共3个对象,这3个对象的类型是Map类型的元素。
【实现方式2】
1)对于【例3-3】,要重新设计SumAction.java,其代码如下。

2)对于显示页面:positive.jsp和negative.jsp,与【实现方式1】中的页面一样。
3.通过IoC访问Servlet对象——Map IoC方式
在Struts 2框架中,通过IoC方式将Servlet对象注入到Action中,需要在Action中实现以下接口中的一个或多个。
(1)org.apache.struts2.interceptor.RequestAware
该接口有void setRequest(Map map)方法,实现该接口可访问HttpServletRequest对象。
(2)org.apache.struts2.interceptor.SessionAware
该接口有void setSession(Map map)方法,实现该接口可访问HttpSession对象。
(3)org.apache.struts2.interceptor.ApplicationAware
该接口有void setApplication(Map map)方法,实现该接口可访问ServletContext对象。
【实现方式3】
1)对于【例3-3】,重新设计Action:SumAction.java,其代码如下。

2)对于显示页面:positive.jsp和negative.jsp,与【实现方式1】中的页面一样。
4.通过IoC访问Servlet对象——Servlet IoC方式
在Struts 2框架中,通过IoC方式将Servlet对象注入到Action中,需要在Action中实现以下接口。
(1)org.apache.struts2.interceptor.ServletContextAware
该接口有void setServletContext(ServletContext servletContext)方法,实现该接口的Action可以直接访问ServletContext对象。
(2)org.apache.struts2.interceptor.ServletRequestAware
该接口有void setServletRequest(HttpServletRequest ruquest)方法,实现该接口的Action可以直接访问HttpServletRequest对象。
(3)org.apache.struts2.interceptor.ServletResponseAware
该接口有void setServleResponse(HttpServletResponse response)方法,实现该接口的Action可以直接访问HttpServletResponse对象。
【实现方式4】
1)对于【例3-3】,重新设计Action:SumAction.java,其代码如下。

2)对于显示页面:positive.jsp和negative.jsp,与【实现方式1】中的页面一样。
本小节给出了4种从Action获取Servlet对象的方法,在实际应用中,可以根据项目特点选择其中之一。
3.5.5 基于注解的Action配置
前面介绍的Action定义和应用都是采用XML配置文件实现Action的配置与调用的,当应用程序较复杂时,配置文件会很庞大、复杂。
Struts 2提供了注解开发jar包:struts2-convention-plugin-2.3.*.jar,实现了注释配置。使用时,需要复制struts-2.2.1\lib\struts2-convention-plugin-2.3.*.jar到当前Web应用的Web-INF的lib目录下。
Struts 2使用注解开发必须遵循的规范:第一,Action必须要继承ActionSupport父类;第二,Action所在的包名必须以.action结尾。
基于注解的配置,分为在类级别上的注解和在方法级别上的注解两种。
1.标注在Action类上方的注释——实现包注解配置
其注释格式如下。
@ParentPackage(value="要继承的包名称") //表示继承的父包 @Namespace(value="/命名空间名称") //表示当前Action所在的命名空间
其中各参数的含义如下。
● @ParentPackage:对应xml配置文件中的package的父包,一般需要继承struts-default。
● @Namespace:对应配置文件中的nameSpace,命名空间。
2.标注在Action类中方法上面的注解——实现Action注解配置
其注释格式如下。
@Action( //表示请求的Action及处理方法 value="login", //表示Action的请求名称 results={ //表示结果跳转 @Result(name="success", location="/success.jsp", type="redirect"), @Result(name="login", location="/login.jsp", type="redirect"), @Result(name="error", location="/error.jsp", type="redirect") }, )
其中,该注释中的属性有以下两个。
● value="名称":表示Action的请求名称,也就是<action>结点中的name属性。
● results={}:表示Action的多个result,是一个数组(集合)属性。
3.对于Action的属性results中@Result的注解
其注释格式如下。
@Result(name=" ", location=" ", type=" "),
其中各属性的含义如下。
● name="名称",表示Action方法返回值,即<result>结点的name属性,默认为success。
● location="路径名",表示要跳转的新响应位置,既可以是相对路径,也可以是绝对路径。
● type="跳转类型",框架默认的是dispatcher。
通过这样注解开发,可以代替配置xml的编写。在下一节中将给出具体的应用案例。