北京,雾霾天气阻止了今天的马拉松之行,蜗居一天。为一个问题“struts2如何保证ActionContext每次取的都是本次请求所对应的实例?”,给一个网友解释了半天。
首先,我们知道,struts2和struts1的一个重要区别就是它进行了Action类和Servlet的解耦。而又提供了获取Servlet API的其它通道,就是ActionContext(别跟我说还有个ServletActionContext,其实ServletActionContext只是ActionContext的一个子类而已)。源码为证:
1
|
public
class
ServletActionContext
extends
ActionContext
implements
StrutsStatics
|
其次,他也知道,ActionContext是Action执行时的上下文,可以看作是一个容器,并且这个容器只是一个Map而已,在容器中存放的是Action在执行时需要用到的VALUE_STACK、ACTION_NAME、SESSION、APPLICATION、ACTION_INVOCATION等等对象,还可以存放自定义的一些对象。我想用过struts2的朋友们,大多也都知道这些吧。
第三,他奇怪的是,在一个请求的处理过程拦截器、action类和result中任何时候获取的ActionContext都是跟当前请求绑定那一个。为什么!?
我给他的建议是,带着问题读源码,呵呵。那我们一起来看看吧:
首先是ActionContext类的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public
class
ActionContext
implements
Serializable{
static
ThreadLocal actionContext =
new
ThreadLocal();
public
static
final
String ACTION_NAME =
"com.opensymphony.xwork2.ActionContext.name"
;
public
static
final
String VALUE_STACK =
"com.opensymphony.xwork2.util.ValueStack.ValueStack"
;
public
static
final
String SESSION =
"com.opensymphony.xwork2.ActionContext.session"
;
public
static
final
String APPLICATION =
"com.opensymphony.xwork2.ActionContext.application"
;
public
static
final
String PARAMETERS =
"com.opensymphony.xwork2.ActionContext.parameters"
;
public
static
final
String LOCALE =
"com.opensymphony.xwork2.ActionContext.locale"
;
public
static
final
String TYPE_CONVERTER =
"com.opensymphony.xwork2.ActionContext.typeConverter"
;
public
static
final
String ACTION_INVOCATION =
"com.opensymphony.xwork2.ActionContext.actionInvocation"
;
public
static
final
String CONVERSION_ERRORS =
"com.opensymphony.xwork2.ActionContext.conversionErrors"
;
public
static
final
String CONTAINER =
"com.opensymphony.xwork2.ActionContext.container"
;
Map<String, Object> context;
public
ActionContext(Map<String, Object> context)
{
this
.context = context;
}
//... ...
public
static
void
setContext(ActionContext context)
{
actionContext.set(context);
}
public
static
ActionContext getContext()
{
return
(ActionContext)actionContext.get();
}
public
void
setContextMap(Map<String, Object> contextMap)
{
getContext().context = contextMap;
}
public
Map<String, Object> getContextMap()
{
return
this
.context;
}
//... ...
public
void
setSession(Map<String, Object> session)
{
put(
"com.opensymphony.xwork2.ActionContext.session"
, session);
}
public
Map<String, Object> getSession()
{
return
(Map)get(
"com.opensymphony.xwork2.ActionContext.session"
);
}
//... ...
public
Object get(String key)
{
return
this
.context.get(key);
}
public
void
put(String key, Object value)
{
this
.context.put(key, value);
}
}
|
源码清晰的说明了我们编程中再熟悉不过的一行代码:ActionContext ctx = ActionContext.getContext();,原来我们所取得的ctx来自于ThreadLocal啊!熟悉ThreadLocal的朋友都知道它是与当前线程绑定的,而且是我们Java中处理多线程问题的一种重要方式。我们再看,类中有个Map类型的变量context,其实,它才是前面我们提到的真正意义上的“容器”,用来存放Action在执行时所需要的那些数据。
到这里,他最初的那个问题已经很了然了。但是,他紧接着又一个疑惑提出来了:“那既然每个请求处理线程都有自己的ActionContext,那里面的那些数据是什么时候放进去的呢”?
这次我给他的建议是,动脑筋,用源码验证。既然ActionContext存放有HttpServletRequest及其中的参数,既然ActionContext贯穿于整个请求处理过程,那就从struts2请求处理的入口(过滤器StrutsPrepareAndExecuteFilter)找,源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public
class
StrutsPrepareAndExecuteFilter
implements
StrutsStatics, Filter
{
// ... ...
public
void
doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws
IOException, ServletException
{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try
{
this
.prepare.setEncodingAndLocale(request, response);
this
.prepare.createActionContext(request, response);
//就是在这里进行创建并初始化ActionContext实例
this
.prepare.assignDispatcherToThread();
if
((
this
.excludedPatterns !=
null
) && (
this
.prepare.isUrlExcluded(request,
this
.excludedPatterns))) {
chain.doFilter(request, response);
}
else
{
request =
this
.prepare.wrapRequest(request);
ActionMapping mapping =
this
.prepare.findActionMapping(request, response,
true
);
if
(mapping ==
null
) {
boolean
handled =
this
.execute.executeStaticResourceRequest(request, response);
if
(!handled)
chain.doFilter(request, response);
}
else
{
this
.execute.executeAction(request, response, mapping);
}
}
}
finally
{
this
.prepare.cleanupRequest(request);
}
}
//... ...
}
|
再找到prepare对应的类PrepareOperations,查看方法createActionContext(),就一目了然了。
对于ServletActionContext作为ActionContext一个直接子类,原理也是类似的,感兴趣的朋友可以看一下。
帮助别人,同时也是帮助自己。把这个处理的过程记录下来,希望对需要的朋友有所帮助。
本文转自NightWolves 51CTO博客,原文链接:http://blog.51cto.com/yangfei520/1565698,如需转载请自行联系原作者