转载:http://struts2.group.iteye.com/group/wiki/1379-action-in-struts2
多数的MVC框架中的Control层,都是一个Java对象。按照惯例,我们通常会把这个层次上面的Java对象统称为Action层。本篇文章,我们就来简单介绍一下Struts2中Action的相关内容。
Action的定义
更多
传统的MVC框架中,Control层一般都是一个类似与Servlet的一个Java对象。因为从职责上讲,Control层需要完成以下的职责:
1. 接收从Web容器传递过来的参数,并做恰当的类型转化
2. 调用逻辑处理
3. 搜集数据,并返回到视图
而在这个其中的第一步和第三步,都离不开Web容器中的对象的处理。
Struts2中的Action,与其他传统的MVC框架不同,使用了XWork的Action来构造Control层。让我们首先来看看Action的接口定义:
我们只需要实现这个接口,就可以在其中编写业务逻辑完成我们的功能。
在这个接口定义中,我们可以明显看到与传统的MVC框架之间的区别: Struts2中的Action,并不需要依赖于特定的Web容器。我们看不到类似HttpServletRequest,HttpServletResponse等Web容器相关的对象。
而这一点,也带来了问题:
提问:Struts2的Action并不带有任何Web容器相关的对象,Action又是如何工作在Web容器中的呢?
虽然Struts2的Action只是一个非常普通的Java对象,并不具备任何Web容器的特质,但是我们需要把Action放到一个更加大的环境中来看。事实上,Struts2为Action的执行,准备了完整的数据环境和执行环境。而这个执行环境,就保证了Action在Web容器中的顺利运行。
在Struts2中,每个Http的请求,会被发送到一个Filter。而这个Filter,就会针对每个请求,创建出一个代码的执行环境,并在这个基础上,为每个执行环境配备与之对应的数据环境,这个数据环境中的内容,就来自于Web容器中的一个又一个对象。这样,就能够顺利调用Action执行代码而无需担心它是否运行在Web容器中了。
至于这个执行环境和数据环境到底是什么,我们接下来会详细讲到。
提问:Struts2的Action并不带有任何Web容器相关的对象,Action中又如何与Web容器进行通信并获取Web容器的相关对象呢?
刚刚我们提到Struts2会为每个Http的请求建立一个执行环境和数据环境。其中,数据环境就成为了Action获取Web容器的基础。所以,当Action需要获取Web容器的相关对象,需要通过数据环境来进行。
Struts2的Action的这一个重要特性,至少能为我们带来以下好处:
1. 使得Struts2的Action非常易于测试
如果我们完全不考虑Action的执行环境,仅仅把Action看作一个普通的Java对象,那么我们甚至可以直接new一个Action的对象,通过执行其中的方法完成测试。这样,我们就不需要任何的Mock,来模拟Web容器的环境。
2. 结合Action的执行环境,使得Struts2在Control这个层次上,能够定义更加丰富的执行层次
因为Action是一个普通的Java类,而不是一个Servlet类,完全脱离于Web容器,所以我们就能够更加方便地对Control层进行合理的层次设计,从而抽象出许多公共的逻辑,并将这些逻辑脱离出Action对象本身。事实上,Struts2也正是这么做的,无论是Interceptor,还是Result,其实都是抽象出了Action中公共的逻辑部分,将他们放到了Action的外面,从而更加简化了Action的开发。
3. 使得Struts2的Action看上去更像一个POJO,从而更加易于管理
Struts2的Action是一个线程安全的对象。而Web容器传递过来的参数,也会传递到Action中的成员变量中。这样,Action看上去就更像一个POJO,从而能够方便的被许多对象容器进行管理。比如说,你可以非常方便得把Action纳入到Spring的容器中进行管理。
1. 接收从Web容器传递过来的参数,并做恰当的类型转化
2. 调用逻辑处理
3. 搜集数据,并返回到视图
而在这个其中的第一步和第三步,都离不开Web容器中的对象的处理。
Struts2中的Action,与其他传统的MVC框架不同,使用了XWork的Action来构造Control层。让我们首先来看看Action的接口定义:
- /**
- * All actions may implement this interface, which exposes
- * the execute() method. However, as of XWork 1.1, this is
- * not required and is only here to assist users. You are
- * free to create POJOs that honor the same contract
- * defined by this interface without actually implementing
- * the interface.
- */
- public interface Action {
- /**
- * Where the logic of the action is executed.
- *
- * @return a string representing the logical result of the execution.
- * See constants in this interface for a list of standard result values.
- * @throws Exception thrown if a system level exception occurs.
- * Application level exceptions should be handled by returning
- * an error value, such as Action.ERROR.
- */
- public String execute() throws Exception;
- }
/**
* All actions may implement this interface, which exposes
* the execute() method. However, as of XWork 1.1, this is
* not required and is only here to assist users. You are
* free to create POJOs that honor the same contract
* defined by this interface without actually implementing
* the interface.
*/
public interface Action {
/**
* Where the logic of the action is executed.
*
* @return a string representing the logical result of the execution.
* See constants in this interface for a list of standard result values.
* @throws Exception thrown if a system level exception occurs.
* Application level exceptions should be handled by returning
* an error value, such as Action.ERROR.
*/
public String execute() throws Exception;
}
我们只需要实现这个接口,就可以在其中编写业务逻辑完成我们的功能。
- public class Index implements Action {
- private static final long serialVersionUID = -1070481751569420550L;
- /* (non-Javadoc)
- * @see com.opensymphony.xwork2.Action#execute()
- */
- public String execute() throws Exception {
- // write your logic here
- return SUCCESS;
- }
- }
public class Index implements Action {
private static final long serialVersionUID = -1070481751569420550L;
/* (non-Javadoc)
* @see com.opensymphony.xwork2.Action#execute()
*/
public String execute() throws Exception {
// write your logic here
return SUCCESS;
}
}
在这个接口定义中,我们可以明显看到与传统的MVC框架之间的区别: Struts2中的Action,并不需要依赖于特定的Web容器。我们看不到类似HttpServletRequest,HttpServletResponse等Web容器相关的对象。
而这一点,也带来了问题:
提问:Struts2的Action并不带有任何Web容器相关的对象,Action又是如何工作在Web容器中的呢?
虽然Struts2的Action只是一个非常普通的Java对象,并不具备任何Web容器的特质,但是我们需要把Action放到一个更加大的环境中来看。事实上,Struts2为Action的执行,准备了完整的数据环境和执行环境。而这个执行环境,就保证了Action在Web容器中的顺利运行。
在Struts2中,每个Http的请求,会被发送到一个Filter。而这个Filter,就会针对每个请求,创建出一个代码的执行环境,并在这个基础上,为每个执行环境配备与之对应的数据环境,这个数据环境中的内容,就来自于Web容器中的一个又一个对象。这样,就能够顺利调用Action执行代码而无需担心它是否运行在Web容器中了。
至于这个执行环境和数据环境到底是什么,我们接下来会详细讲到。
提问:Struts2的Action并不带有任何Web容器相关的对象,Action中又如何与Web容器进行通信并获取Web容器的相关对象呢?
刚刚我们提到Struts2会为每个Http的请求建立一个执行环境和数据环境。其中,数据环境就成为了Action获取Web容器的基础。所以,当Action需要获取Web容器的相关对象,需要通过数据环境来进行。
Struts2的Action的这一个重要特性,至少能为我们带来以下好处:
1. 使得Struts2的Action非常易于测试
如果我们完全不考虑Action的执行环境,仅仅把Action看作一个普通的Java对象,那么我们甚至可以直接new一个Action的对象,通过执行其中的方法完成测试。这样,我们就不需要任何的Mock,来模拟Web容器的环境。
2. 结合Action的执行环境,使得Struts2在Control这个层次上,能够定义更加丰富的执行层次
因为Action是一个普通的Java类,而不是一个Servlet类,完全脱离于Web容器,所以我们就能够更加方便地对Control层进行合理的层次设计,从而抽象出许多公共的逻辑,并将这些逻辑脱离出Action对象本身。事实上,Struts2也正是这么做的,无论是Interceptor,还是Result,其实都是抽象出了Action中公共的逻辑部分,将他们放到了Action的外面,从而更加简化了Action的开发。
3. 使得Struts2的Action看上去更像一个POJO,从而更加易于管理
Struts2的Action是一个线程安全的对象。而Web容器传递过来的参数,也会传递到Action中的成员变量中。这样,Action看上去就更像一个POJO,从而能够方便的被许多对象容器进行管理。比如说,你可以非常方便得把Action纳入到Spring的容器中进行管理。
Action的生命周期
更多
接下来,我们再来看看Struts2中的Action的生命周期:
这张图来自于Struts2的Reference,我们能够在图中看到许多我们不熟悉的名词,比如ActionProxy,Interceptor等等。这些都是Struts2的Control层的重要元素,也是Struts2的Control层的一个层次化的体现。
上面的这张图基本上能够概括了Struts2的整个生命周期。接下来,我们就对Action中的一些重要元素进行简单的描述。
![](http://downpour.iteye.com/upload/attachment/68182/ae963ed3-fae7-3710-bfcf-2fc49942ee90.png)
这张图来自于Struts2的Reference,我们能够在图中看到许多我们不熟悉的名词,比如ActionProxy,Interceptor等等。这些都是Struts2的Control层的重要元素,也是Struts2的Control层的一个层次化的体现。
上面的这张图基本上能够概括了Struts2的整个生命周期。接下来,我们就对Action中的一些重要元素进行简单的描述。
Action的五大元素
更多
在大概了解了Struts2的Action后,我们来重点研究一下在Struts2的Action周围,为Action进行服务的一些重要元素,这些元素将涵盖Action的数据环境,Action的执行环境、Action的调度者、Action的层次结构和Action的执行结果。
ActionContext —— 数据环境
之前我们提到了Struts2的Action并不是一个Servlet,它是脱离了Web容器的。但是对于一个Web框架来说,所有的数据请求(Request)和数据返回(Response)都来源于Web容器,那么Action在执行的时候,如何去获取这些数据呢?
这个问题的答案就在于,我们需要为每个Action准备一个数据环境,这个数据环境被称之为: ActionContext。由于Action是应对于一个又一个的URL请求,所以 ActionContext应该具备以下的特性:
1. ActionContext应成为Action与Web容器之间的桥梁
2. ActionContext中应该保存有针对某个请求的详细信息
3. ActionContext应该是一个线程安全的类对象
Interceptor —— 丰富的层次结构
简单回顾一下上面所提到的Action的职责,我们看到,需要在Action这个层面上完成的事情还不少。而完成这些职责,就需要我们对这些职责进行合理的分类和排序,将他们组织成有序的执行队列。在Struts2中,使用了一种类似责任链的设计模式对这些不同的职责进行分类并串联起来,从而使得Action层具备了丰富的层次结构。而在这个执行队列中的每个元素,就被我们称之为Interceptor,也就是拦截器。
拦截器是AOP中的概念,它本身是一段代码,可以通过定义“织入点”,来指定拦截器的代码在“织入点”的前后执行,从而起到拦截的作用。正如上面Struts2的Reference中讲述的,Struts2的Interceptor,其拦截的对象是Action代码,可以定义在Action代码之前或者之后执行拦截器的代码。
如果仔细留意一下Action LifeCycle图中的Interceptor和Action的部分,我们可以看到,Interceptor一层一层的把Action包了起来。这是一个典型的堆栈结构,在代码执行的时候,每个Interceptor不仅需要文成它自身的逻辑,还通过递归调用负责下一个拦截器或Action的调用。
也正如Struts2的Reference所说,Struts2提供的绝大多数的功能支持,都通过Interceptor来实现,这些Interceptor可以随意进行配置,并且能够方便的插入到程序中去运行。
Result —— 执行结果
有执行就必然有执行的结果。在Struts2中,Action的执行结果被抽象成了一个层次。在这个层次中,可以定义任意类型的View层的结构。也就是说,Struts2并不强制View层的表现形式,可以是JSP、Freemarker模板、二进制流输出等等。
Struts2把执行结果抽象成一个层次,使得你可以不再关注许多视图整合上面的细节,只需要考虑视图的类型和数据的准备,这样,你也可以不必在沉浸在杂乱的构造视图的代码中。
ActionProxy —— 执行环境
有了拦截器Interceptor,有了Action本身,也有了Action的执行结果Result,我们就需要一个类似调度器的产品,将这些元素整合起来,进行调度执行。在上面的Action Lifecyle的图中,我们可以看到,Interceptor、Action和Result都处于ActionProxy中,所以ActionProxy就成为了所有这些元素的执行环境。
既然是执行环境,那么ActionProxy就需要提供Action执行的时候一切所需要的配置、参数等等,当然,也要有进行Action调用的入口。所以让我们来看一下ActionProxy的接口:
很显然,在这其中,prepare和execute方法是用作Action调用的入口函数,其他的接口定义都与Action执行时的运行参数和配置有关。
ActionInvocation —— 调度者
在上面的ActionProxy的接口定义中,我们可以看到有一个比较特殊的变量: ActionInvocation比较吸引我们的眼球。从字面上去理解,ActionInvocation就是Action的调用者。事实上也是如此,ActionInvocation在这个Action的执行过程中,负责Interceptor、Action和Result等一系列元素的调度。
在之后的章节中,这个ActionInvocation类也将成为我们解读Struts2源码的一个重要入手点。这个类将告诉你,Struts2是如何通过ActionInvocation来实现对Interceptor、Action和Result的合理调度的。
ActionContext —— 数据环境
之前我们提到了Struts2的Action并不是一个Servlet,它是脱离了Web容器的。但是对于一个Web框架来说,所有的数据请求(Request)和数据返回(Response)都来源于Web容器,那么Action在执行的时候,如何去获取这些数据呢?
这个问题的答案就在于,我们需要为每个Action准备一个数据环境,这个数据环境被称之为: ActionContext。由于Action是应对于一个又一个的URL请求,所以 ActionContext应该具备以下的特性:
1. ActionContext应成为Action与Web容器之间的桥梁
2. ActionContext中应该保存有针对某个请求的详细信息
3. ActionContext应该是一个线程安全的类对象
Interceptor —— 丰富的层次结构
简单回顾一下上面所提到的Action的职责,我们看到,需要在Action这个层面上完成的事情还不少。而完成这些职责,就需要我们对这些职责进行合理的分类和排序,将他们组织成有序的执行队列。在Struts2中,使用了一种类似责任链的设计模式对这些不同的职责进行分类并串联起来,从而使得Action层具备了丰富的层次结构。而在这个执行队列中的每个元素,就被我们称之为Interceptor,也就是拦截器。
Struts2 Reference 写道
Interceptors can execute code before and after an Action is invoked.
拦截器是AOP中的概念,它本身是一段代码,可以通过定义“织入点”,来指定拦截器的代码在“织入点”的前后执行,从而起到拦截的作用。正如上面Struts2的Reference中讲述的,Struts2的Interceptor,其拦截的对象是Action代码,可以定义在Action代码之前或者之后执行拦截器的代码。
如果仔细留意一下Action LifeCycle图中的Interceptor和Action的部分,我们可以看到,Interceptor一层一层的把Action包了起来。这是一个典型的堆栈结构,在代码执行的时候,每个Interceptor不仅需要文成它自身的逻辑,还通过递归调用负责下一个拦截器或Action的调用。
Struts2 Reference 写道
Most of the framework's core functionality is implemented as Interceptors. Features like double-submit guards, type conversion, object population, validation, file upload, page preparation, and more, are all implemented with the help of Interceptors. Each and every Interceptor is pluggable, so you can decide exactly which features an Action needs to support.
也正如Struts2的Reference所说,Struts2提供的绝大多数的功能支持,都通过Interceptor来实现,这些Interceptor可以随意进行配置,并且能够方便的插入到程序中去运行。
Result —— 执行结果
有执行就必然有执行的结果。在Struts2中,Action的执行结果被抽象成了一个层次。在这个层次中,可以定义任意类型的View层的结构。也就是说,Struts2并不强制View层的表现形式,可以是JSP、Freemarker模板、二进制流输出等等。
Struts2把执行结果抽象成一个层次,使得你可以不再关注许多视图整合上面的细节,只需要考虑视图的类型和数据的准备,这样,你也可以不必在沉浸在杂乱的构造视图的代码中。
ActionProxy —— 执行环境
有了拦截器Interceptor,有了Action本身,也有了Action的执行结果Result,我们就需要一个类似调度器的产品,将这些元素整合起来,进行调度执行。在上面的Action Lifecyle的图中,我们可以看到,Interceptor、Action和Result都处于ActionProxy中,所以ActionProxy就成为了所有这些元素的执行环境。
既然是执行环境,那么ActionProxy就需要提供Action执行的时候一切所需要的配置、参数等等,当然,也要有进行Action调用的入口。所以让我们来看一下ActionProxy的接口:
- public interface ActionProxy {
- /**
- * Called after all dependencies are set
- */
- void prepare() throws Exception;
- /**
- * @return the Action instance for this Proxy
- */
- Object getAction();
- /**
- * @return the alias name this ActionProxy is mapped to
- */
- String getActionName();
- /**
- * @return the ActionConfig this ActionProxy is built from
- */
- ActionConfig getConfig();
- /**
- * Sets whether this ActionProxy should also execute the Result after executing the Action
- *
- * @param executeResult
- */
- void setExecuteResult(boolean executeResult);
- /**
- * @return the status of whether the ActionProxy is set to execute the Result after the Action is executed
- */
- boolean getExecuteResult();
- /**
- * @return the ActionInvocation associated with this ActionProxy
- */
- ActionInvocation getInvocation();
- /**
- * @return the namespace the ActionConfig for this ActionProxy is mapped to
- */
- String getNamespace();
- /**
- * Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
- * ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
- *
- * @return the result code returned from executing the ActionInvocation
- * @throws Exception
- * @see ActionInvocation
- */
- String execute() throws Exception;
- /**
- * Sets the method to execute for the action invocation. If no method is specified, the method provided by
- * in the action's configuration will be used.
- *
- * @param method the string name of the method to invoke
- */
- void setMethod(String method);
- /**
- * Returns the method to execute, or null if no method has been specified (meaning "execute" will be invoked)
- */
- String getMethod();
- }
public interface ActionProxy {
/**
* Called after all dependencies are set
*/
void prepare() throws Exception;
/**
* @return the Action instance for this Proxy
*/
Object getAction();
/**
* @return the alias name this ActionProxy is mapped to
*/
String getActionName();
/**
* @return the ActionConfig this ActionProxy is built from
*/
ActionConfig getConfig();
/**
* Sets whether this ActionProxy should also execute the Result after executing the Action
*
* @param executeResult
*/
void setExecuteResult(boolean executeResult);
/**
* @return the status of whether the ActionProxy is set to execute the Result after the Action is executed
*/
boolean getExecuteResult();
/**
* @return the ActionInvocation associated with this ActionProxy
*/
ActionInvocation getInvocation();
/**
* @return the namespace the ActionConfig for this ActionProxy is mapped to
*/
String getNamespace();
/**
* Execute this ActionProxy. This will set the ActionContext from the ActionInvocation into the ActionContext
* ThreadLocal before invoking the ActionInvocation, then set the old ActionContext back into the ThreadLocal.
*
* @return the result code returned from executing the ActionInvocation
* @throws Exception
* @see ActionInvocation
*/
String execute() throws Exception;
/**
* Sets the method to execute for the action invocation. If no method is specified, the method provided by
* in the action's configuration will be used.
*
* @param method the string name of the method to invoke
*/
void setMethod(String method);
/**
* Returns the method to execute, or null if no method has been specified (meaning "execute" will be invoked)
*/
String getMethod();
}
很显然,在这其中,prepare和execute方法是用作Action调用的入口函数,其他的接口定义都与Action执行时的运行参数和配置有关。
ActionInvocation —— 调度者
在上面的ActionProxy的接口定义中,我们可以看到有一个比较特殊的变量: ActionInvocation比较吸引我们的眼球。从字面上去理解,ActionInvocation就是Action的调用者。事实上也是如此,ActionInvocation在这个Action的执行过程中,负责Interceptor、Action和Result等一系列元素的调度。
在之后的章节中,这个ActionInvocation类也将成为我们解读Struts2源码的一个重要入手点。这个类将告诉你,Struts2是如何通过ActionInvocation来实现对Interceptor、Action和Result的合理调度的。