1. Struts2与MVC
-
1.1 基本的MVC架构
模型-视图-控制器或通常被称为 MVC,是一种用于开发web应用程序的软件设计模式.MVC模式由以下三个部分组成:
Model:模式的最低层,负责维护数据.
View:负责显示全部或部分的数据给用户.
Controller: 控制模型和视图之间的交互的软件代码.
MVC抽象用图形表示如下:
-
1.2 Struts2的MVC
Struts2是一个pull-MVC(或 MVC2)框架。Struts 2 的模型-视图-控制器模式由下面的五个核心部件实现:
** 动作,拦截器,值栈/OGNL,结果/结果类型,视图技术**.
控制器是由 Struts2调度servlet过滤器和拦截器实现的.
模型是由Action(包括ActionForm)实现的.
视图是由JSp,FreeMaker等技术实现的,抽象成结果.
值栈/OGNL,结果/结果类型提供共同主线,连接和集成其他组件.
2. Struts架构
这张图很好地反应了Struts2的整个框架.
FilterDispatcher:整个Struts2的调度中心(现在用StrutsPrepareAndExecuteFilter),也就是MVC中的C(控制中心),根据ActionMapper的结果来决定是否处理请求,如果ActionMapper指出该URL应该被Struts2处理,那么它将会执行Action处理,并停止过滤器链上还没有执行的过滤器.
ActionMapper:会判断这个请求是否应该被Struts2处理,如果需要Struts2处理,ActionMapper会返回一个对象来描述请求对应的ActionInvocation的信息.
ActionProxy:它会创建一个ActionInvocation代理实例,位于Action和xwork之间,使得我们在将来有机会引入更多的实现方式,比如通过WebService来实现等.
ConfigurationManager:是xwork配置的管理中心,通俗的讲,可以把它看做struts.xml这个配置文件在内存中的对应.
struts.xml是Stuts2的应用配置文件,负责诸如URL与Action之间映射的配置、以及执行后页面跳转的Result配置等.
ActionInvocation:真正调用并执行Action,它拥有一个Action实例和这个Action所依赖的拦截器实例.ActionInvocation会执行这些拦截器、Action以及相应的Result.类似于调度器.
Interceptor(拦截器):拦截器是一些无状态的类,拦截器可以自动拦截Action,它们给开发者提供了在Action运行之前或Result运行之后来执行一些功能代码的机会。类似于我们熟悉的javax.servlet.Filter.
Action:动作类是Struts2中的动作执行单元。用来处理用户请求,并封装业务所需要的数据.
Result:Result就是不同视图类型的抽象封装模型,不同的视图类型会对应不同的Result实现,Struts2中支持多种视图类型,比如Jsp,FreeMarker等.
Templates:各种视图类型的页面模板,比如JSP就是一种模板页面技术.
Tag Subsystem:Struts2的标签库,它抽象了三种不同的视图技术JSP、velocity、freemarker,可以在不同的视图技术中,几乎没有差别的使用这些标签.
3. Struts2运行流程
-
用户发出请求
请求会被Tomcat接收到,Tomcat服务器来选择处理这个请求的Web应用,就是选择哪一个web工程来处理这个请求. -
web容器去读这个工程的web.xml
在web.xml中进行匹配,发现是由struts2的过滤器FilterDispatcher(StrutsPrepareAndExecuteFilter)来处理,找到该过滤器的实例(初始化). -
找到FilterDispatcher,回调doFilter()
通常情况下,web.xml文件中还有其他过滤器时,FilterDispatcher是放在滤器链的最后;如果在FilterDispatcher前出现了如SiteMesh这种特殊的过滤器,还必须在SiteMesh前引用Struts2的ActionContextCleanUp过滤器. -
FilterDispatcher将请求转发给ActionMapper
ActionMapper负责识别当前的请求是否需要Struts2做出处理. -
ActionMapper告诉FilterDispatcher,需要处理这个请求,建立ActionProxy
FilterDispatcher会停止过滤器链以后的部分,所以通常情况下:FilterDispatcher应该出现在过滤器链的最后。然后建立一个ActionProxy对象,这个对象作为Action与xwork之间的中间层,会代理Action的运行过程. -
ActionProxy询问ConfigurationManager,读取Struts.xml
ActionProxy对象刚被创建出来的时候,并不知道要运行哪个Action,它手里只有从FilterDispatcher中拿到的请求的URL.这时候,它问ConfigurationManager问到底要运行哪个Action.
而ConfigurationManager就是负责读取并管理struts.xml的,可以简单的理解为ConfigurationManager是struts.xml在内存中的映像.
在服务器启动的时候,ConfigurationManager会一次性的把struts.xml中的所有信息读到内存里,并缓存起来,以保证ActionProxy拿着来访的URL向他询问要运行哪个Action的时候,就可以直接匹配、查找并回答了. -
ActionProxy建立ActionInvocation对象
ActionProxy拿到了运行哪个Action、相关的拦截器以及所有可能使用的result信息,就可以着手建立ActionInvocation对象了,ActionInvocation对象描述了Action运行的整个过程. -
在execute()之前的拦截器
在execute()之前会执行很多默认的拦截器.拦截器的运行被分成两部分,一部分在Action之前运行,一部分在Result之后运行,而且顺序是刚好反过来的。也就是在Action执行前的顺序,比如是拦截器1、拦截器2、拦截器3,那么运行Result之后,再次运行拦截器的时候,顺序就变成拦截器3、拦截器2、拦截器1了。 - 执行execute()方法
- 根据execute方法返回的结果,也就是Result,在struts.xml中匹配选择下一个页面
- 找到模版页面,根据标签库生成最终页面
- 在execute()之后执行的拦截器,和8相反
-
ActionInvocation对象执行完毕
这时候已经得到了HttpServletResponse对象了,按照配置定义相反的顺序再经过一次过滤器,向客户端展示结果.
4. 再看Struts2架构与流程
Struts2本身,也包含了真正意义上的Struts2与Xwork两种框架.从职责上来说,XWork才是真正实现MVC的框架,Struts2的工作是在对Http请求进行一定处理后,委托XWork完成真正的逻辑处理.将Web容器与MVC实现分离解耦,是Struts2的精妙之处
-
4.1 Struts2部分
在第二节的框架图中,过滤器部分均属于Struts2部分.
包括StrutsPrepareFilter与StrutsExecuteFilter,是StrutsPrepareAndExecuteFilter的两部分.
前者是Struts2进行Http请求的预处理,而后者是Struts2进行Http请求的逻辑处理.
StrutsPrepareAndExecuteFilter自2.1.3替代FilterDispatcher.如果想自定义过滤器, 要放在strtus2过滤器之前,又想在执行action之前拿filter做一些事,FilterDispatcher做不到.而StrutsPrepareAndExecuteFilter可以拆分成StrutsPrepareFilter和StrutsExecuteFilter,可以在这两个过滤器之间加上自己的过滤器.
-
4.2 XWork部分
在离开web容器以后,就到了Xwork部分,执行业务逻辑.它包括了ActionProxy,ActionInvocation,Interceptor,Action,ActionContext,ValueStack,Result七个元素.
该图很直观地描绘了Xwork的整体架构(不包括Dispatcher),包括了第二节架构图中的大部分,我们称之为控制流元素.另外增加了ActionContext与ValueStack这样的数据流元素.
关于控制流元素,在第二节已有相关介绍,接下来看看数据流元素的内容:
ActionContext-数据环境
ActionContext是一个独立的数据结构,其主要作用是为XWork的执行提供数据环境。无论是请求的参数,还是处理的返回值,甚至一些原生的Web容器对象,都被封装于ActionContext的内部,成为了Struts2 / XWork执行时所依赖的数据基础.职责在于数据存储.
ValueStack-数据访问环境
ValueStack本身是一个数据结构,其主要作用是用以对OGNL计算进行扩展。因而,位于ActionContext之中的ValueStack则赋予了ActionContext进行数据计算的功能,从而使得ValueStack自身成为了一个可以进行数据访问的环境.职责在于数据传输.
-
4.3 Struts2初始化
第二节的图对应当请求发生时候的过程.那么这一整个初始的框架是怎么产生的呢.其实在Struts2框架处理http请求,Xwork框架处理业务逻辑之前,还需要Struts2框架的初始化过程.
StrutsPrepareAndExecuteFilter是整个框架的入口点,在初始化时候调用init()方法.
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();//类似一个工具类,包含了一些初始化操作
Dispatcher dispatcher = null;//Dispatcher:Struts2的核心分发器
try {
/**
* 封装filterConfig,提供了一个便利的方法
* getInitParameterNames(),将枚举类型的参数转换成Iterator(EnumerationIterator)
*/
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);//初始化日志
//初始化Dispatcher
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);//初始化静态文件加载器
prepare = new PrepareOperations(dispatcher);//初始化HTTP预处理的操作类
execute = new ExecuteOperations(dispatcher);//初始化进行HTTP请求处理的逻辑执行操作类
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);//回调方法,留作用户拓展
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}
可以看到大致的初始化过程分为以下几步:
- 封装FilterConfig->FilterHostConfig
- 初始化日志操作
- 初始化核心分发器Dispatcher
- 初始化静态文件加载器,packages,该参数用来配置自动搜寻目录
- 初始化PrepareOperations和ExecuteOperations.
其中我们再来看核心分发器Dispatcher的初始化过程:
public void init() {
//初始化配置文件管理器
if (configurationManager == null) {
//根据name进行对象寻址
//DEFAULT_BEAN_NAME = "struts"
//<bean type="org.apache.struts2.dispatcher.DispatcherErrorHandler" name="struts".../>
//<bean class="com.opensymphony.xwork2.ObjectFactory" name="struts"/>
configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
}
try {
init_FileManager(); //初始化文件管理器
// 初始化Struct2的默认配置加载器:
// org/apache/struts2/default.properties,
// 如果项目中需要覆盖,可以在classpath里的struts.properties里覆写
init_DefaultProperties(); // [1]
//初始化Xml配置加载器:
// 如struts-default.xml,struts-plugin.xml,struts.xml
init_TraditionalXmlConfigurations(); // [2]
//初始化Properties配置加载器
init_LegacyStrutsProperties(); // [3]
//初始化用户自定义的配置加载器
init_CustomConfigurationProviders(); // [5]
//初始化由web.xml传入的参数
init_FilterInitParameters() ; // [6]
//初始化容器内置的对象
//eg:ObjectFactory,FreemarkerManager....
init_AliasStandardObjects() ; // [7]
//创建容器, 初始化并预加载配置
Container container = init_PreloadConfiguration();
//对容器进行依赖注入
container.inject(this);
//检查对WebLogic的特殊支持
init_CheckWebLogicWorkaround(container);
//初始化所有的DispatcherListener
if (!dispatcherListeners.isEmpty()) {
for (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(this);
}
}
//初始化错误处理器
errorHandler.init(servletContext);
} catch (Exception ex) {
if (LOG.isErrorEnabled())
LOG.error("Dispatcher initialization failed", ex);
throw new StrutsException(ex);
}
}
这里还可以分为四部:
- 初始化ConfigurationManager
- 初始化配置加载器.常用的配置文件加载顺序如
a. default.properties
b. struts-default.xml
c. struts-plugin.xml
d. struts.xml
e. struts.properties
f. web.xml - 初始化容器(创建容器,依赖注入)
- 额外的初始化工作(初始化WebLogic配置,执行DispatcherListner的配置..)
4.4 再看Struts2流程
根据上面的总结,从一个更高的抽象角度看,我们把流程分为三部分,也即初始化部分,Struts2处理http请求部分,Xwork处理业务逻辑部分.