深入struts2_控制流体系
1. Action
-
1.1 请求相应模式
请求-响应模式是一种概念非常宽泛的人机交互模式,是人与计算机进行沟通的一种最基本的行为方式.
那么在web应用开发过程中都是如何实现的呢.我们会有三种不同的实现方式.
参数-返回值
这是一种最为直观的请求-响应的实现模式.方法参数对应请求内容,返回值对应结果响应.
参数-参数
最底层的Servlet就是基于此模式设计的,而实际上底层规范都不得不采用这样的实现机制.
PoJo
这也就是struts2所采用的请求相应模式了,所有的请求对象都以内部属性变量的方式存在,而且所有的返回值同样是以该形式存在.相当于,POJO对某一次的响应是一个有状态的响应.
-
2. struts2与springMVC的解决方案
springMVC采用了一种游离于参数-参数模式与参数-返回值模式之间,并整合了各自优势的实现模式,而struts2则是POJO模式.从三个方面比较各自优劣:
请求参数:在这一点上参数比属性变量更为直观,更符合java原生的语法含义.springMVC略胜一筹.
响应数据:对于纯粹的响应数据,最适合的仍然是方法的返回值.然而响应必须包含响应数据以及响应跳转逻辑,而框架的困境在于很难同时表达数据和控制两个方面.SpringMVC使用一个ModelAndView的对象将不同的逻辑处理分开,使用返回值来表达响应流程控制,而为响应方法增加一个Model对象,用以存储数据.Struts2就简单很多,仍然可以用内部属性存储数据.
所以这个方面很难讲孰优孰劣.
响应控制:在上面讲了,springMVC使用ModelAndView处理数据和控制,而struts2直接用返回值处理响应控制即可.
总得来说,在数据访问上struts2有天然的优势.
-
3. action的突破
回到action本身,action实现了对servlet的两大突破.
与web容器无关
关于这一点,显而易见我们看不到任何和web容器有关的对象.action只是一个普通的java对象.这使得action非常易于测试,看上去更像一个POJO,易于管理,并且使得struts2在控制器这个层面上有更多的发挥余地.
响应对象有状态
action对象表现出两种截然不同的特性,即属性特征与行为特征.从语法角度讲,前者指变量的属性,而后者指的是响应的过程.action是一个状态与动作的结合体,天然符合了响应类的基本要素.action的状态自然解毒为请求数据与响应数据.
2. Interceptor
-
1.AOP编程
Interceptor本身就是属于AOP的概念,基本实质为一个代码段,通过定义切入点,来指定Interceptor的代码逻辑在切入点之前或之后执行,从而起到"拦截"的作用.
暂且不去说AOP的基本概念的含义,但我们可以看到struts2中的Interceptor与AOP之间的对应
切面Aspect:Interceptor实现
通知Advice:Around通知
切入点Pointcut:Action对象
连接点Joinpoint:Action执行
-
2. 内部结构与执行方式
对于Interceptor的内部结构,有几点讲
群居:Interceptor对象是一个群居对象,在同一时刻总有多个对象协同工作.
栈结构:这是一个典型的栈结构,而Action位于栈的底部,由于后入先出,所以action才最后执行.那么也许有疑问,不是action之后还要倒着再来一遍Interceptor么.其实这里是用递归调用完成的一个巧妙数据结构.
关于执行方式,来看其一个实现类的源码
public String intercept(ActionInvocation invocation) throws Exception{
return invocation.invoke();
}
intercept方法以ActionInvocation作为参数,我们知道它是XWork控制流元素的核心调度元素,以它为参数方便自身与其他元素的交互,其次也便于ActionInvocation对自己的调度.
再来看方法的主体,只有一句invocation.invoke()
.这里有两个含义,其一是有责任把执行的控制权交给栈的下一个元素去执行.其二是如果返回一个String类型的Result就中止栈的调度.
-
3. AOP还是IOC
Spring是AOP的一个典范,可以以一句话总结:Spring是用IOC实现AOP的.
那么回到XWork的Interceptor上,它的AOP实现与Spring的差别在于对于切入点的定于不同.Spring侧重于方法级别的AOP拦截,而XWork针对Action对象的拦截.
拦截Action对象之后干嘛呢,对其内部属性变量进行管理,影响其运行状态的改变,从而对其行为进行扩展.
换个角度看,内部属性与方法有啥用,构成Action这个响应类与外部环境的交互接口,构成请求数据与响应数据,构成自身行为动作与业务逻辑处理对象之间的交互协作.而这正是依赖关系的体现(环境依赖,数据依赖,协作依赖).
也就是XWork使用AOP实现IOC的.
-
4. Interceptor的意义
可以从不同的角度看到Interceptor存在的价值.
Interceptor对象的引入,是对事件处理过程中主次职责的有效划分
Interceptor对象的引入能丰富整个事件处理流程的执行层次,为AOP编程打下结实的数据结构基础
Interceptor使得责任链模式在整个执行栈的调度过程中顺利实施
3. ActionInvocation
ActionInvocation是控制流的核心调度元素.
来看其源码
public interface ActionInvocation extends Serializable{
Object getAction();
boolean isExecuted();
ActionContext getInvocationContext();
ActionProxy getProxy();
Result getResult() throws Exception;
String getResultCode();
void setResultCode(String resultCode);
ValueStack getStack();
void addPreResultListener(PreResultListener listener);
String invoke() throws Exception;
String invokeActionOnly() throws Exception;
void setActionEventListener(ActionEventListener listener);
void init(ActionProxy proxy) ;
}
大致可以根据接口方法的不同作用进行逻辑分类
对控制流元素和数据流元素的访问接口
getAction,getActionProxy,getStack等等
对执行调度流程的扩展接口
addPreListner,setActionEventListner等等
对执行栈进行不调度执行的接口
invoke,invokeActionOnly;
-
3.1 调度分析
ActionInvocation的核心调度功能是通过invoke方法实现,对Interceptor和Action对象共同构成的执行栈进行逻辑执行调度.
看看源码中的invoke()方法
public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
if (!executed) {
if (preResultListeners != null) {
for (Object preResultListener : preResultListeners) {
PreResultListener listener = (PreResultListener) preResultListener;
String _profileKey = "preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally {
UtilTimerStack.pop(_profileKey);
}
}
}
// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
return resultCode;
}
finally {
UtilTimerStack.pop(profileKey);
}
}
我们可以看到如果有下一个拦截器,会不断交给下一个执行,如果没有拦截器了就执行action,,最后执行result的对象逻辑.那么action执行完毕之后倒序的拦截器去哪了呢?
这里就要说递归的巧妙运用,以invocation.invoke为分水岭,递归使得分水岭之后的代码被暂时封存,而去执行栈的下一个元素,等完整的递归调用结束之后,再触发整个逆序的执行过程.
4. ActionProxy
ActionProxy是Xwork事件处理框架的总代理
public interface ActionProxy {
Object getAction();
String getActionName();
ActionConfig getConfig();
void setExecuteResult(boolean executeResult);
boolean getExecuteResult();
ActionInvocation getInvocation();
String getNamespace();
String execute() throws Exception;
String getMethod();
boolean isMethodSpecified();
}
看源码可知,ActionProxy也是一个复杂的接口,身处比ActionInvocation更高的层次,主要职责是维护Xwork执行元素与请求对象直接的映射关系.
再来看ActionProxy的初始化,同样是由工厂类实现,在工厂类接口中找到createActionProxy方法
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext);
可以看到需要的参数包括
配置关系映射:namespace,actionName,methodName等等..
运行上下文环境:extraContext,cleanupContext
5. 数据流与控制流的交互
2这张图在讲架构时候类似地出现过.而这里着重于数据流与控制流的交互上.
首先ActionContext横跨了整个Xwork的控制流架构,意味着它的生命周期有那么长.由于ActionContext使用Threadlocal模式实现,也表明它甚至对整个线程都是有效的.
再来看ValueStack,它环绕着Action存在,可以说action是数据流与控制流的交互点.看源码便能一目了然
public void init(ActionProxy proxy){
if(pushAction){
stack.push(action);
contextMap.put("action",action);
}
}
而action的交互体系分为三部分即外部环境交互体系,数据交互体系,协作交互体系.
第一个最主要指action与web容器中对象的交互过程.一方面可以使用servletActionContext实现,另一方面也可以使用ServletConfigInterceptor等拦截器通过IOC注入实现.
第二个就是指与数据流元素的交互了,请求数据,响应数据与属性变量的映射关系,主要由ParametersInterceptor拦截器完成.
第三个指将struts2看成一个门户,获得业务逻辑操作对象的过程,例如与Spring的整合由ActionAutowiringInterceptor完成.
6. result
作为控制流元素的最后一个,result是非常特殊的.一方面接口定义在XWork体系中,但实现类确应算Struts2体系之中,它告诉程序该何去何从.
action似乎把HttpServletResponse的职责都承包了,接口与实现的分离实现了框架级别的解耦合,为control层与view层架起桥梁.
如果说action本身并不实现解耦,而是通过数据流或者AOP拦截器等手段加以实现,那么result本身就是起到了解耦的作用.
仔细思考result的职责,主要有三个:
起到一个中转作用,实现控制权的转移:即从Xwork到Struts2的转移
需要进行两大框架的解耦:从struts2的角度看,这是一个典型的策略模式.
需要一个web容器的代理操作对象:屏蔽底层的细节.