Struts请求处理原理图如下:
Struts2请求分派由filter完成。目前提供了两种不同的运用情况:
StrutsPrepareAndExecuteFilter完成了原FilterDispatcher的功能,使用该filter时必须把其配置在所有filter的最后。但是,无法满足这样一种应用场景:希望在struts2的环境下做一些过滤器的操作。因此strutsPrepareFilter+StrutsExecuteFilter用于满足上述的需求。它将环境的加载和分派执行action分开来。
strutsPrepareFilter主要功能即在init中根据filterConfig初始化了Dispatcher(该类是真正的请求分派功能实现类),并由此Dispatcher的初始化触发了框架的初始化工作。另外,解析出一个需要过滤的请求后缀列表以及调用一个待扩展的回调方法。然后在doFilter中,也即截获到请求并处理之前完成了如下的处理:设置编码及Local,创建ActionContext,绑定当前线程与dispatcher,如果非过滤请求则包装request(这个包装在FileUploadIntercepter中有很好的应用),解析出actionmaping。
StrutsExecuteFilter的主要功能即在doFilter中执行了指定的action。具体流程是:如果是过滤的请求,则直接chain.doFilter到过滤链的下一个。获取PrepareFilter中已经准备好的环境。如果是静态资源的请求则使用静态资源加载器处理静态资源。否则executeAction。值得一提的是,在这里的静态资源判断的条件与StrutsPrepareAndExecuteFilter想比多了一个recursionCounter>1的条件,作者的提示指出该条件意味着当前是在一个forward的执行过程中,这个时候request里会残留着在forward前解析的mapping,如果处理将会导致死循环。该条件意味着forward过程不难理解,现在需要明白的是为什么PrepareAndExecuteFilter里同样的判断没有这个条件呢?它也应该会面临着forword时重复mapping的问题?问题的关键在于findActionMapping方法的最后一个参数forceLookUp(抛弃request残留)。ExecuteFilter为了降低开销直接得到PrepareFilter中解析出来的mapping,而将forceLookup设置为了false。
struts接受请求的入口是在web.xml中配置的过滤器
<filter>
<filter-name>struts-pre</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter</filter-class>
<init-param>
<param-name>config</param-name>
<param-value>struts-default.xml,config/struts/struts.xml</param-value>
</init-param>
</filter>
<filter>
<filter-name>struts-execute</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-pre</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>struts-execute</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
StrutsPrepareFilter用于预处理request请求然后交给StrutsExecuteFilter去处理,StrutsExecuteFilter的doFilter方法被自动调用
StrutsExecuteFilter的doFilter处理过程如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//如果对应的URL在当前过滤器被过滤掉了,就跳到下一个Filter处理
if (excludeUrl(request)) {
chain.doFilter(request, response);
return;
}
// This is necessary since we need the dispatcher instance, which was created by the prepare filter
if (execute == null) {
lazyInit();
}
ActionMapping mapping = prepare.findActionMapping(request, response);
//if recusrion counter is > 1, it means we are in a "forward", in that case a mapping will still be
//in the request, if we handle it, it will lead to an infinte loop, see WW-3077
Integer recursionCounter = (Integer) request.getAttribute(PrepareOperations.CLEANUP_RECURSION_COUNTER);
if (mapping == null || recursionCounter > 1) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
execute.executeAction(request, response, mapping);
}
}
mapping是如何获取的呢?
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} catch (Exception ex) {
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}
return mapping;
}
首先介绍一下过滤器Filter,
过滤器都要实现Filter 接口:Filter接口有如下方法
init():这个方法在容器实例化过滤器时被调用,它主要设计用于使过滤器为处理做准备。容器为这个方法传递一个FilterConfig,其中包含有配置信息。
doFilter():过滤器拥有单个用于处理请求和响应的方法doFilter()。这个方法接收3个输入参数: ServletRequest、ServletResponse和FilterChain。FilterChain对于正确的过滤操作至关重要,doFilter()方法必须调用FilterChain的doFilter()方法,除非该方法用来拦截以后的下游处理。
destroy():该方法由容器在销毁过滤器实例之前调用,以便能够执行任何必需的清理代码。
过滤器通过 web.xml 文件中的两个XML标签来声明。<filter>标签定义过滤器的名称,并且声明实现类和init()参数。<filter-mapping>标签将过滤器与Servlet或URL模式相关联。<filter>标签负责把一个过滤器名和一个特定的类关联起来,这种关联是通过<filter-name>和<filter-class>元素指定的。<filter>必须有一个<ulr-pattern>或者<servlet-name>元素,可以通过<ulr-pattern>来指定通配符,将过滤器应用到Web资源范围;也可以通过<servlet-name>将过滤器指定到某一个特定的Servlet上。应该注意这些声明的顺序,所引用的过滤器名必须在前面的过滤器定义中给出。
参考:http://yangw101860.iteye.com/blog/1440212
http://developer.51cto.com/art/201208/354943.htm