您的位置:首页 > 编程语言 > Java开发

Struts2-04-struts2工作原理

2016-02-08 17:40 531 查看

1. 简介

首先呢,网上讲述struts2的早期版本,从FilterDispatcher(现在已过时)讲起的太多了,然后我将针对比较新的版本的struts2原理进行讲解。核心过滤器是:

<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


然而虽然核心过滤器改变了,但是struts2的工作原理基本和以往的基本一致,官方架构图如下:



2. StrutsPrepareAndExecuteFilter 简介

首先看一下类的介绍:



意思呢就是这个过滤器的处理分为准备(preparation)和执行(execution)两个过程,看过滤器名字就能知道。那么这个过滤器的准备阶段肯定是初始化配置在init方法中执行,执行阶段肯定就是在doFilter方法之中了。

struts2的大致执行流程:

1. 启动服务器的时候对过滤器进行初始化,struts2大致完成PrepareOperations对象和ExecuteOperations对象的初始化,还有一个比较重要的对象Dispatcher对象的初始化(基本是初始化默认配置以及用户的struts.xml配置)。

2. 当拦截到一个请求之后,首先创建一个ActionContext对象,并初始化request,session,parameters,application,attr和值栈,将这些对象存放到ActionContext中,然后将ActionContext的这个实例存放到ThreadLocal中。这里到时候在具体分析,这里就是对ActionContext进行初始化。

3. 通过对请求url的解析获取ActionMapping对象,并判断这次请求是否需要让struts2进行处理。

4. 如果由struts2进行处理的话,就创建ActionProxy对象执行execute()方法。

5. 然后根据配置(ConfigManager)创建ActionInvocation的实例,调用invoke()方法。方法中就是对拦截器链的递归调用以及对最后Action的执行,当执行完毕返回一个结果串。

6. 最后根据这个返回串确定是转发,重定向还是到另一个Action等的操作,将jsp或者FreeMarker模板响应给浏览器。

3. 核心过滤器解析

下面就对上面的执行过程一个个来分析。可能会不是很详细,不过自己可以跟踪代码去看看。。若是有什么错误还望指出。

3.1 成员变量

protected PrepareOperations prepare;
protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns = null;


第一个和第二个顾名思义是整个过滤器比较重要的两个对象(准备和执行)。第三个参数表示不包含的匹配表达式,实际是用来过滤掉不包含的url的。这里先说一下不太重要的 excludedPatterns:

this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
/**
跟踪代码进去,其实是将常量"struts.action.excludePattern"配置的表达式封装成一个List.
这里比如可以在struts.xml文件中配置不包含css,js等url的处理.
如配置:
<constant name="struts.action.excludePattern" value="/res/.*,/css/.*,/images/.*,/js/.*,/services/.*" />
参考:http://xingguangsixian.iteye.com/blog/2088617
*/


初始化这个成员变量之后,那么在doFilter()处理如下:

if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
......
......
/**
在这里就是对请求url和excludedPatterns的判断,如果包括的正则包含url,那么就不处理,过滤器直接放行.
*/


3.2 init()方法

public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);

prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

postInit(dispatcher, filterConfig);
} finally {
if (dispatcher != null) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}


1. 首先是对config进行一下包装,内部没什么特别的。然后initLogging,看函数名也知道对日志的初始化,内部也比较简单,如果config指定了loggerFactory的类,那么就用用户自定义的,否则使用系统默认的。

2. 初始化dispatcher,这句话过滤器执行时间比较长的一部分。不过跟踪进去大致可以知道是对各种配置文件进行的初始化,包括用户定义的struts.xml文件。

3. 然后是对prepare和execute进行初始化,其实这两个类的主要操作也是依赖dispatcher对象的。

4. 初始化excludedPatterns对象,这里不多说了。

5. postInit()方法,是个protected函数(是个空函数),可以继承进行覆盖,作为用户自定义初始化的一个回调。

6. 最后finally清理下struts的ActionContext等对象。

3.3 doFilter()函数

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
execute.executeAction(request, response, mapping);
}
}
} finally {
prepare.cleanupRequest(request);
}
}


1. 将request和response强转下,没啥多说。

**2. **if判断的内容上面说过,不在累赘。主要看else的部分,是对struts2处理请求和响应的部分。

3. 设置编码(如果是get请求获取参数,自行编写过滤器。网上的教程一般都有讲解),然后是创建ActionContext上下文。创建上下文主要是针对当前的请求,将request,response,application,值栈ValueStack,parameters,attr等对象放置到ActionContext中,实际是ognl上下文。

4. 将当前的prepare对象通过ThreadLocal存放到当前线程中。然后是对request进行包装,这里的包装主要正对普通参数和文件上传两种request。

MultiPartRequestWrapper是StrutsRequestWrapper的子类,比较重要的就是StrutsRequestWrapper对getAttribute()方法的覆盖,这里在上篇博文已经讲述过,这就是为什么在jsp中使用EL表达式也能获取到ActionContext中变量值的原因。



5. 然后通过解析url获取ActionMapping对象,解析出对应struts.xml文件的Action的各个属性(name,namespace,method等等)。然后判断如果存在映射就使用execute对象执行Action,否则放行。

6. 最后清理一下整个请求的环境。

3.4 执行Action

实际调用dispatcher的serviceAction()方法。如果按照正常的请求流程,内部会创建一个ActionProxy代理对象,执行execute()方法。在这个方法中调用ActionInvocation对象的实例执行invoke()方法。自己内部跟踪看看就知道了。

然后是invoke()方法:

if (interceptors.hasNext()) {
final InterceptorMapping interceptor = interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
resultCode = invokeActionOnly();
}


这段代码可以看出是对拦截器链的递归执行,这也就是为什么我们自定义拦截器的时候,不返回字符串就要编写invocation.invoke()的原因。

如果拦截器执行完毕,那么就执行Action,内部细节就不多说了。

这个方法中,最后获取到返回的result之后,就对result进行处理,这里面就包含很多了,如转发,重定向,另一个Action啊等等。

4. 总结

其实StrutsPrepareAndExecuteFilter的工作流程也不是很难,关键就是在一些细节方面的处理。这里就简单聊一聊它的简单原理,其实也不是特别的难。结合代码在看看框架图,整个struts2的工作结果基本就略知一二了。每天进步一点点,大家加油。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  struts2.0 工作原理