struts2 工作原理浅析

声明:这是转载的

最近学习struts2,其实它就是webwork2.2的升级版,现附上原理图 


上图来源于Struts2官方站点,是Struts 2 的整体结构。 
一个请求在Struts2框架中的处理大概分为以下几个步骤 
1 客户端初始化一个指向Servlet容器(例如Tomcat)的请求 
2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin) 
3 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action 
4 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy 
5 ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类 
6 ActionProxy创建一个ActionInvocation的实例。 
7 ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。 
8 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper 

在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。

===============================华丽的分隔线============================

Struts2 2013

这是转帖,源帖地址 http://downpour.iteye.com/blog/295414

Strutst的骨架 Interceptor - Action - result
Struts 的核心是Action的调度机制(interceptor)
数据流向是Struts的粘合剂(ognl)

Struts2 重点有三个部分

Interceptor  Action Result 理解了这三部分就基本上读懂了Struts2

一 OGNL —— 数据运转的催化剂

为了解决数据从View层传递到Controller层时的不匹配性,Struts2采纳了XWork的OGNL方案。
并且在OGNL的基础上,构建了OGNLValueStack的机制,从而比较完美的解决了数据流转中的不匹配性。

OGNL api

/** 
 * Evaluates the given OGNL expression tree to extract a value from the given root 
 * object. The default context is set for the given context and root via 
 * <CODE>addDefaultContext()</CODE>. 
 * 
 * @param tree the OGNL expression tree to evaluate, as returned by parseExpression() 
 * @param context the naming context for the evaluation 
 * @param root the root object for the OGNL expression 
 * @return the result of evaluating the expression 
 * @throws MethodFailedException if the expression called a method which failed 
 * @throws NoSuchPropertyException if the expression referred to a nonexistent property 
 * @throws InappropriateExpressionException if the expression can't be used in this context 
 * @throws OgnlException if there is a pathological environmental problem 
 */  
public static Object getValue( Object tree, Map context, Object root ) throws OgnlException;  
  
/** 
 * Evaluates the given OGNL expression tree to insert a value into the object graph 
 * rooted at the given root object.  The default context is set for the given 
 * context and root via <CODE>addDefaultContext()</CODE>. 
 * 
 * @param tree the OGNL expression tree to evaluate, as returned by parseExpression() 
 * @param context the naming context for the evaluation 
 * @param root the root object for the OGNL expression 
 * @param value the value to insert into the object graph 
 * @throws MethodFailedException if the expression called a method which failed 
 * @throws NoSuchPropertyException if the expression referred to a nonexistent property 
 * @throws InappropriateExpressionException if the expression can't be used in this context 
 * @throws OgnlException if there is a pathological environmental problem 
 */  
public static void setValue( Object tree, Map context, Object root, Object value ) throws OgnlException  

OGNL三要素
表达式(Expression)
根对象(Root)
上下文环境(Context) 上下文环境是一个Map结构
Struts参数传递:
这个参数传递的过程主要指数据从View层传递到Control层时Struts2的工作方式。我们知道,Struts2完成参数传递处理工作的基础是OGNL和ValueStack。
使用OGNL的最基本的功能,就能完成普通的Java对象的赋值工作。
ActionContext是Struts2中OGNL的上下文环境Struts2的Action是OGNL操作的根对象。


二 Action,MVC的核心控制器 
Struts2中的Action,并不需要依赖于特定的Web容器。我们看不到类似HttpServletRequest,HttpServletResponse等Web容器相关的对象。
Action五大元素
1 ActionContext —— Action的数据环境
2 Interceptor —— 丰富的层次结构 
3 Result —— 执行结果 4 ActionProxy —— Action执行环境 5 ActionInvocation —— 调度者 
ActionInvocation比较吸引我们的眼球。从字面上去理解,ActionInvocation就是Action的调用者。事实上也是如此,ActionInvocation在这个Action的执行过程中,负责Interceptor、Action和Result等一系列元素的调度。 这个ActionInvocation类也将成为我们解读Struts2源码的一个重要入手点。这个类将告诉你,Struts2是如何通过ActionInvocation来实现对Interceptor、Action和Result的合理调度的。


三 拦截器,让Action有更宽广的延伸空间
如图


1. 整个结构就如同一个堆栈,除了Action以外,堆栈中的其他元素是Interceptor 
2. Action位于堆栈的底部。由于堆栈"先进后出"的特性,如果我们试图把Action拿出来执行,我们必须首先把位于Action上端的Interceptor拿出来执行。这样,整个执行就形成了一个递归调用 
3. 每个位于堆栈中的Interceptor,除了需要完成它自身的逻辑,还需要完成一个特殊的执行职责。这个执行职责有3种选择: 

1) 中止整个执行,直接返回一个字符串作为resultCode 

2) 通过递归调用负责调用堆栈中下一个Interceptor的执行 

3) 如果在堆栈内已经不存在任何的Interceptor,调用Action 
Struts2的拦截器结构的设计,实际上是一个典型的责任链模式的应用。
Interceptor的接口的定义:

public interface Interceptor extends Serializable {  
  
    /** 
     * Called to let an interceptor clean up any resources it has allocated. 
     */  
    void destroy();  
  
    /** 
     * Called after an interceptor is created, but before any requests are processed using 
     * {@link #intercept(com.opensymphony.xwork2.ActionInvocation) intercept} , giving 
     * the Interceptor a chance to initialize any needed resources. 
     */  
    void init();  
  
    /** 
     * Allows the Interceptor to do some processing on the request before and/or after the rest of the processing of the 
     * request by the {@link ActionInvocation} or to short-circuit the processing and just return a String return code. 
     * 
     * @return the return code, either returned from {@link ActionInvocation#invoke()}, or from the interceptor itself. 
     * @throws Exception any system-level error, as defined in {@link com.opensymphony.xwork2.Action#execute()}. 
     */  
    String intercept(ActionInvocation invocation) throws Exception;  
} 




intercept方法是实现整个拦截器机制的核心方法。而它所依赖的参数ActionInvocation则是我们之前章节中曾经提到过的著名的Action调度者。
来看看一个典型的Interceptor的抽象实现类:
public abstract class AroundInterceptor extends AbstractInterceptor {  
      
    /* (non-Javadoc) 
     * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation) 
     */  
    @Override  
    public String intercept(ActionInvocation invocation) throws Exception {  
        String result = null;  
  
        before(invocation);  
        // 调用下一个拦截器,如果拦截器不存在,则执行Action  
        result = invocation.invoke();  
        after(invocation, result);  
  
        return result;  
    }  
      
    public abstract void before(ActionInvocation invocation) throws Exception;  
  
    public abstract void after(ActionInvocation invocation, String resultCode) throws Exception;  
  
}  




所以,我们可以发现,invocation.invoke()这个方法其实是整个拦截器框架的实现核心。基于这样的实现机制,我们还可以得到下面2个非常重要的推论: 

1. 如果在拦截器中,我们不使用invocation.invoke()来完成堆栈中下一个元素的调用,而是直接返回一个字符串作为执行结果,那么整个执行将被中止。 
2. 我们可以以invocation.invoke()为界,将拦截器中的代码分成2个部分,在invocation.invoke()之前的代码,将会在Action之前被依次执行,而在invocation.invoke()之后的代码,将会在Action之后被逆序执行。 

由此,我们就可以通过invocation.invoke()作为Action代码真正的拦截点,从而实现AOP。


源码解析 invoke()方法

ActionInvocation是Struts2中的调度器,所以事实上,这些代码的调度执行,是在ActionInvocation的实现类中完成的,这里,我抽取了DefaultActionInvocation中的invoke()方法,它将向我们展示一切。 

public String invoke() throws Exception {
	try{
		// 依次调用拦截器堆栈中的拦截器代码执行  
        if (interceptors.hasNext()) {  
            final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();  
            UtilTimerStack.profile("interceptor: "+interceptor.getName(),   
                    new UtilTimerStack.ProfilingBlock<String>() {  
                        public String doProfiling() throws Exception {  
                         // 将ActionInvocation作为参数,调用interceptor中的intercept方法执行  
                            resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);  
                            return null;  
                        }  
            });  
        } else {  
            resultCode = invokeActionOnly();  
        } 
		return resultCode; 
	} 
	finally{
		...
	}
}


resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
表面上,它只是执行了拦截器中的intercept方法,如果我们结合拦截器来看,就能看出点端倪来:
public String intercept(ActionInvocation invocation) throws Exception {  
    String result = null;  
  
        before(invocation);  
        // 调用invocation的invoke()方法,在这里形成了递归调用  
        result = invocation.invoke();  
        after(invocation, result);  
  
        return result;  
}


原来在intercept()方法又对ActionInvocation的invoke()方法进行递归调用,ActionInvocation循环嵌套在intercept()中,一直到语句result = invocation.invoke()执行结束。这样,Interceptor又会按照刚开始执行的逆向顺序依次执行结束。 

一个有序链表,通过递归调用,变成了一个堆栈执行过程,将一段有序执行的代码变成了2段执行顺序完全相反的代码过程,从而巧妙地实现了AOP。这也就成为了Struts2的Action层的AOP基础。
Robbin点评:
Spring是用IoC来实现AOP,而Xwork是用AOP来实现IoC,整个Xwork的架构就是完全基于AOP之上的 这句话太绝妙了,
我补充一下,错了不要打我板子:(,之所以说Spring是用IoC来实现AOP这个是spring的核心,而最具代表性的
应该是其通过配置文件来实现IoC的控制思想;而Xwork是用AOP来实现IoC,最具代表性的就是其interceptor。


个人评语:
*AOP个人通俗的理解为无缝织入,即把原本独立不相干的业务逻辑织入到一个现有的系统中去。
在这里理解为把拦截器Interceptor织入到Action中。



问:正序执行intercept还好理解,但是递归到后面执行action以及逆序执行intercept就不太好理解了?
interceptor是一个栈结构,先进后出。
你需要理解的是,当interceptor1执行调用interceptor2的时候,interceptor1并没有执行完毕,而是被暂时搁置了,
因此因此等到action执行完毕的时候,必然是逆序执行之前被搁置的所有代码。(downpour


四 Result 视图
Result的首要职责,是封装Action层到View层的跳转逻辑。
Struts2自带的ServletDispatcherResult的跳转逻辑

HttpServletRequest request = ServletActionContext.getRequest();  
RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);  
....  
dispatcher.forward(request, response); 

Redirect重定向为例,在Struts2自带的ServletRedirectResult中,也同样存在着重定向的核心代码
HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);  
....  
response.sendRedirect(finalLocation); 


Result定义接口
public interface Result extends Serializable {  
  
    /** 
     * Represents a generic interface for all action execution results, whether that be displaying a webpage, generating 
     * an email, sending a JMS message, etc. 
     */  
    public void execute(ActionInvocation invocation) throws Exception;  
}


这个接口定义非常简单,通过传入ActionInvocation,执行一段逻辑。


常用的Result 
1
dispatcher 
Xml代码
<result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>  
dispatcher主要用于返回JSP,HTML等以页面为基础View视图,这个也是Struts2默认的Result类型。
例:
<result name="success">/index.jsp</result> 

2
redirect 
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>  
<result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
2.1 chain
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> 


再来谈谈chain,之前提到,chain其实只是在一个action执行完毕之后,forward到另外一个action,所以他们之间是共享HttpServletRequest的。
在使用chain作为Result时,往往会配合使用ChainingInterceptor。
chain这个Result有一些常用的使用情景,
比如说,一张页面中,你可能有许多数据要显示,而某些数据的获取方式可能被很多不同的页面共享
(典型来说,“推荐文章”这个小栏目的数据获取,可能会被很多页面所共享)。
这种情况下,可以把这部分逻辑抽取到一个独立Action中,并使用chain,将这个Action与主Action串联起来。
这样,最后到达页面的时候,页面始终可以得到每个Action中的数据。 


3
stream
<result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
StreamResult等价于在Servlet中直接输出Stream流。这种Result被经常使用于输出图片、文档等二进制流到客户端。
通过使用StreamResult,我们只需要在Action中准备好需要输出的InputStream即可。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值