首页 > WEB开发 > 后台开发 > Struts2工作流程、源码分析
2014
09-30

Struts2工作流程、源码分析

12. Struts2工作流程、源码分析17

本文主要对Struts2的工作流程作源码级别的深入分析。在后面也会对Struts2的结果类型、拦截器、异常处理进行剖析。

所有的一切都要从Struts2控制器StrutsPrepareAndExecuteFilter开始(就简称SPEF吧)。

12. Struts2工作流程、源码分析141

  • 当Tomcat启动时,实例化Struts2控制器,并在init方法中完成了Struts2框架的初始化。
  • 当收到一次浏览器请求时(本文只考虑Action请求),进入doFilter方法,该方法主要完成了Struts2框架的调度处理,分为preparation、execution两个过程。

一、Strut2框架的初始化:init

1、初始化阶段(init)

12. Struts2工作流程、源码分析321

框架配置文件加载的说明:Dispatcher.init

  • init_DefaultProperties:加载org/apache/struts2/default.properties
  • init_TraditionalXmlConfigurations:依次加载struts-default.xml、struts-plugin.xml、struts.xml。这三种配置文件都在classpath的根目录,且dtd约束是一样的,如果出现相同的元素,后者覆盖前者(☆)

总结:init阶段发生在Tomcat启动后,主要做了:

① 创建并初始化dispatcher对象,在dispatcher对象初始化的过程中,加载struts2配置文件。

② 实例化prepare和execute对象,这两个对象在后面的prepare阶段和execute阶段会用到。

2、Struts容器

在struts-default.xml中配置了很多bean元素,同Spring框架中的bean元素一样,这些bean会在init阶段被实例化,并放入Struts容器中。下面是Struts容器中的一些常见bean:

图片1

① ObjectFactory:创建对象的工厂,这些对象包括Action对象、结果类型、拦截器、验证器、类型转换器等。

② ValueStackFactory:创建ValueStack对象的工厂。针对用户的每次action请求,都会创建一个ValueStack对象存放数据。

③ ActionProxyFactory:创建Action类代理对象的工厂。

④ ActionMapper:如果用户请求访问一个Action,则通过ActionMapper.getMapping返回一个代表action元素的ActionMapping类,否则返回null。即该类提供了在用户请求到action调用的映射功能。

Q:如何在程序中获取Struts容器中的Bean? Container.getInstance(Class typeClazz);

Container对象的获取有两种方式:Configuration.getContainer、Dispatcher.getContainer。
如在prepare阶段会根据用户请求获取Mapping对象,对应的源码是:

mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(..);在创建ValueStack、创建ActionProxy对象时都需要用到。

3、Struts2与Spring整合原理

1)改变action对象创建方式

① 创建一个类MyObjectFactory,继承ObjectFactory类,并覆写其中的buildAction方法(或buildBean方法)

② 在struts.xml配置文件中声明自己的对象创建工厂就OK了。

<bean type="com.opensymphony.xwork2.ObjectFactory" name="myObjFactory" class="org.flyne.MyObjectFactory" />

<!--  覆盖default.properties中的相应配置 -->
<constant name="struts.objectFactory" value="myObjFactory" />

上面的原理很简单,就是用自己定义的对象工厂覆盖掉Struts2提供的ObjectFactory。

2)Struts2 + Spring整合原理

在整合Struts2与Spring时,需要引入struts-spring-plugin.jar包,在struts-plugin.xml文件中,有如下配置:

<bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />

<constant name="struts.objectFactory" value="spring" />

整合的原理就是将创建action的工作交给了StrutsSpringObjectFactory,在该类中覆写了buildBean方法,先从Spring容器中查找相应的action,找不到再利用反射创建创建Action。

二、Action请求的处理流程

1、准备阶段(prepare)

说明:下图logo覆盖部分是Dispatcher。

12. Struts2工作流程、源码分析2258

总结:prepare阶段发生在用户发出请求之后,主要做了:

① 创建值栈(☆):首先创建ValueStack对象(第3步),把request / session / application中的值封装成一些map,再把这些map放入到ValueStack中的大map(第5步),把大map传递给ActionContext(第6步),说明ValueStack和ActionContext中引用的map是同一份。

② 包装request对象

③ 根据用户请求查找action元素,并返回代表action元素的actionMapping对象

2、执行阶段(execute)

说明:下图logo覆盖部分是ActionInvocation。

12. Struts2工作流程、源码分析2544

核心源码:

----------------------第3步:创建ActionProxy----------------------
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false);
-------------第4步:执行拦截器、动作方法、结果类型-------------
proxy.execute();

补充:创建ActionProxy的过程(图中的第3步)

12. Struts2工作流程、源码分析2858

总结:execute阶段发生在找到与请求对应的action元素(mapping != null)时,主要做了:

① actionProxyFactory.createProxy:创建action的代理对象,然后调用ActionInvocation.init方法,该方法中又做了两件事:创建了action对象,并将action对象放入值栈(压入root,并放到context);获取拦截器列表及相应的迭代器。

② actionProxy.execute(☆):会去执行ActionInvocation.invoke,从而触发了责任链(执行拦截器、action、结果集,然后反向再来一次),关于责任链中的执行流程见《责任链设计模式》一文。

三、其他问题

1、结果类型

在Struts2中,可以实现Result接口或继承StrutsResultSupport类来自定义一个结果类型。Struts2提供的结果类型的类层次结构如下所示:

12. Struts2工作流程、源码分析3274

StrutsResultSupport抽象类的execute方法如下:

public void execute(ActionInvocation invocation) throws Exception {
    lastFinalLocation = conditionalParse(location, invocation);
    doExecute(lastFinalLocation, invocation);
}

conditionalParse解析result配置中的location参数(该参数中可能出现OGNL表达式),在最后调用了doExecute()抽象方法。所以继承StrutsResultSupport类时,只需实现其中的doExecute方法即可。

Q:自定义结果类型时,什么时候实现Result接口,什么时候继承StrutsResultSupport类?

都可以,但是如果需要使用location属性,即转发或跳转到一个页面/action时,直接继承StrutsResultSupport类并实现doExecute方法比较方便,否则直接实现Result接口就行。

2、拦截器

1)modelDriven和params拦截器:使用模型驱动封装表单数据

modelDriven拦截器用于将模型对象压入栈顶,前提是动作类必须实现ModelDriven接口。modelDriven拦截器中的intercept方法如下:

public String intercept(ActionInvocation invocation) {
    Object action = invocation.getAction();

    if (action instanceof ModelDriven) {
        ModelDriven modelDriven = (ModelDriven) action;
        ValueStack stack = invocation.getStack();
        Object model = modelDriven.getModel();
        if (model !=  null) {
        	stack.push(model);
        }
    }
    return invocation.invoke();
}

params拦截器用于将页面的表单数据封装成Map,并调用ValueStack.setValue设置栈顶对象的属性值。相关源码不再列出。

不难推出,Struts2的默认拦截器栈中,modelDriven在params之前。在使用模型驱动封装表单数据时,首先将模型对象压入栈顶,再改变栈顶对象的属性值;使用属性驱动封装表单数据时,modelDriven拦截器不起作用,此时栈顶对象为Action对象。

2)validation和workflow拦截器:共同处理Struts2的验证问题

12. Struts2工作流程、源码分析4549

3、异常处理

1)Struts2的默认异常处理

异常的处理有两种方式:throws和catch。默认情况下,Struts2框架中的异常会沿着拦截器 –> Dao –> Service –> Action –> 结果类型 –> 拦截器一直抛(throws),最后回到Dispatcher.serviceAction中进行捕获处理。Dispatcher.serviceAction中的核心代码如下:

public void serviceAction(...){
    try{
        ActionProxy proxy = actionProxyFactory.createActionProxy();
        proxy.execute();
    } catch(Exception e){
        sendError(...);
}
}

从ServiceAction的源码中可以看出,如果在执行action的过程中(proxy.execute)出现异常,就会调用sendError方法,该方法将页面转发到一个FreeMarker编写的模板页面(/org/apache/struts2/dispatcher/error.ftl),于是就会看到每次Struts2报错时的页面,如下:

12. Struts2工作流程、源码分析5114

2)exception拦截器:可以自定义全局异常处理

exception拦截器位于Struts2拦截器栈的栈顶,其intercept方法如下:

public String intercept(ActionInvocation invocation) throws Exception {
    String result;
    try {
        result = invocation.invoke();
    } catch (Exception e) {
        List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
        String mappedResult = this.findResultFromExceptions(exceptionMappings, e);
        if (mappedResult != null) {
            result = mappedResult;//找到action-mapping元素配置的result属性
            publishException(invocation, new ExceptionHolder(e));//将异常压入栈顶
        } else {
            throw e;
        }
    }

    return result;
}

由拦截器栈的执行顺序可知,在责任链上的方法出现异常时,会一路抛到exception拦截器,从而被exception拦截器的catch部分捕获,该拦截器对异常的处理是这样的:首先在struts.xml中查找exception-mapping元素,找到返回exception-mapping元素对应的result属性值并将异常对象压入值栈栈顶;如果找不到,继续抛,此时异常就进入了Dispatcher.serviceAction中被捕获。

下面用一个Demo来说明exception拦截器的使用。

① 在struts.xml中配置exception-mapping:exception-mapping元素必须配在global-exception-mapping之间:

<package name="ep" extends="struts-default">
	<global-results>
		<result name="errHandler" type="chain">
			<param name="actionName">exceptionProcessor</param>
		</result>
	</global-results>

	<global-exception-mappings>
		<exception-mapping result="errHandler" exception="java.lang.Exception"></exception-mapping>
	</global-exception-mappings>

	<action name="exceptionProcessor" class="org.flyne.test.ExceptionProcessor">
		<result>error.jsp</result>
	</action>
</package>

② 创建异常处理Action:当责任链上的方法产生异常时,找到了相应的exception-mapping元素,然后执行name=”errHandler”的结果集,结果类型需设置为chain,这样才能转发到ExceptionProcessor中处理:

public class ExceptionProcessor extends ActionSupport{
	private Exception exception;

	public Exception getException() {
		return exception;
	}
	public void setException(Exception exception) {
		this.exception = exception;
	}

	public String execute() throws Exception {			ActionContext.getContext().getValueStack().push(exception.getMessage());
		System.out.println("捕获了异常:"+this.exception.getMessage());
		return SUCCESS;
	}
}

ExceptionProcessor配置的结果视图(error.jsp)如下:

<s:debug></s:debug>
<s:property/>

③ 测试:配置一个会产生异常的动作类GenerateException:

public class GenerateException extends ActionSupport{
	public String execute() throws Exception {
		int i = 1/ 0;
		return super.execute();
	}
}
------------------------------------------
<package name="test" extends="ep">
	<action name="testAction" class="org.flyne.test.GenerateException">
		<result>index.jsp</result>
	</action>
</package>

当访问http://localhost:8080/proj_name/testAction时,由于会产生异常,因此会被exception拦截器捕获,并传到ExceptionProcessor进行处理。


Struts2工作流程、源码分析》有 1 条评论

  1. 赞一个,楼主辛苦了!

留下一个回复

你的email不会被公开。