近期在看struts2,在看到论坛上有人分析了StrutsPrepareAndExecuteFilter的源码,感觉这个类是很核心的,可以知道struts2拦截到用户请求之后是如何对参数进行转换的。我就按图索骥,也来走读一下这个类的代码,大家多指教。
在使用struts的时候要在web.xml中配置一个过滤器,来拦截用户发起的请求,并进行一些预处理,根据配置文件把请求分配给对应的action并将请求中的参数与action中的字段进行对应赋值。现在就来解读一下,这背后都发生了哪些事情。
在一些介绍struts2的博客中,使用的过滤器是FilterDispatcher,在文档中我们可以看到如下的说明:
Deprecated.Since Struts 2.1.3, use StrutsPrepareAndExecuteFilter instead or StrutsPrepareFilter and StrutsExecuteFilter if needing using the ActionContextCleanUp filter in addition to this one
StrutsPrepareAndExecuteFilter的说明如下:
Handles both the preparation and execution phases of the Struts dispatching process. This filter is better to use when you don't have another filter that needs access to action context information, such as Sitemesh.
这2个说明就不做过多的说明了。Web.xml中配置过滤器的代码片段如下:
<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>
要构建一个过滤器很简单就是实现javax.servlet.Filter接口即可。这个接口有三个方法:
init() :这个方法在容器实例化过滤器时被调用,它主要设计用于使过滤器为处理做准备。该方法接受一个FilterConfig 类型的对象作为输入。
doFilter() :与 servlet 拥有一个 service() 方法(这个方法又调用 doPost() 或者 doGet() )来处理请求一样,过滤器拥有单个用于处理请求和响应的方法doFilter() 。这个方法接受三个输入参数:一个 ServletRequest 、 response 和一个 FilterChain 对象。
destroy() :正如您想像的那样,这个方法执行任何清理操作,这些操作可能需要在自动垃圾收集之前进行。
正如文档中的描述,use StrutsPrepareAndExecuteFilter instead or StrutsPrepareFilter and StrutsExecuteFilter,在StrutsPrepareAndExecuteFilter类中有2个字段,分别声明了StrutsPrepareFilter与StrutsExecuteFilter的实例。现在先来看看init方法
public void init(FilterConfig filterConfig) throws ServletException {
//一个集中了多个初始化方法的工具类
InitOperations init = new InitOperations();
try {
//包装javax.servlet.FilterConfig对象
//重新实现了getInitParameterNames()方法
//把返回值由Enumeration类型转化为Iterator类型
FilterHostConfig config = new FilterHostConfig(filterConfig);
//初始化日志
init.initLogging(config);
//创建Dispatcher 包含了2方面的信息:servletcontext,拦截器的配置参数
//同时指定了初始化配置文件的顺序
Dispatcher dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
//PrepareOperations为请求处理做一些准备工作
prepare =
new PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute =
new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
//哪些请求是被过滤掉的, 不执行拦截器
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
//回调方法
postInit(dispatcher, filterConfig);
} finally {
init.cleanup();
}
}
新建dispatcher的代码如下:
private Dispatcher createDispatcher( HostConfig filterConfig ) {
Map<String, String> params = new HashMap<String, String>();
for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext();){
String name = (String) e.next();
String value = filterConfig.getInitParameter(name);
params.put(name, value);
}
return new Dispatcher(filterConfig.getServletContext(), params);
}
private void init_DefaultProperties() {
configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
}
doFilter是过滤器的执行方法,它拦截提交的HttpServletRequest请求,HttpServletResponse响应,是strtus2的核心拦截器。简单的分析如下:
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
//设置字符编码集
prepare.setEncodingAndLocale(request, response);
//创建actioncontext
prepare.createActionContext(request, response);
//Dispater有一个静态的Threadlocal变量,为每个线程保存一个副本
prepare.assignDispatcherToThread();
if ( excludedPatterns != null &&
prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
//如果是文件上传请求会封装称:MultiPartRequestWrapper
//否则为:StrutsRequestWrapper
//封装之后可以访问actioncontext,并使用OGNL表达式了
request = prepare.wrapRequest(request);
//No mapping will be created in the case of static resource requests
//or unidentifiable requests for other servlets
ActionMapping mapping = prepare.findActionMapping(
request, response, true);
if (mapping == null) {
//Tries to execute a request for a static resource
//对这个没有一个太感性的认识
boolean handled =
execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
//处理请求,读取配置文件,利用反射机制生成一个action代理
execute.executeAction(request, response, mapping);
}
}
} finally {
prepare.cleanupRequest(request);
}
}
为请求新建一个actioncontext的代码如下:
public ActionContext createActionContext(
HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
Integer counter = 1;
//暂时不知道这个计数器有啥用
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// detected existing context, so we are probably in a forward
ctx = new ActionContext(
new HashMap<String, Object>(oldContext.getContextMap()));
} else {
ValueStack stack = dispatcher.getContainer().getInstance
(ValueStackFactory.class).createValueStack();
//stack.getContext()就是个Map<String, Object>
//dispatcher.createContextMap()封装了http请求相关的信息
//比如:request,reponse,ServletContext,http parameters,session,application
//感觉所谓的context其实就可以认为是一个集合类,保存了http请求的相关信息
stack.getContext().putAll(dispatcher.createContextMap(
request, response, null, servletContext));
//通过这里我们看出来,actioncontext包含了哪些东西:
//request,reposne,session,application中的参数以及valuestack。
//通过OGNL获取数据的方式可以得到对应
ctx = new ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
ActionContext.setContext(ctx);
return ctx;
}
另外一篇不错的帖子
http://www.iteye.com/topic/829843
联系客服