一、Struts运行流程图先了解一下:
引用网上的总结:
一个请求在Struts2框架中的处理大概分为以下几个步骤:
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
3、接着FilterDispatcher(现已过时)被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。
二、值栈:简单的说,就是存放action的堆栈,当我们提交一个请求道服务器端 action时,就有个堆栈,如果action在服务器端进行跳转,所有action共用一个堆栈,当需要保存在action中的数据时,首先从栈顶开始 搜索,若找到相同的属性名(与要获得的数据的属性名相同)时,即将值取出。但这种情况可能出现找到的值不是我们想要的值,那么解决此问题需要用TOP语法 和N语法来进行解决。
三、值栈是怎样创建的以及值栈和ActionContext的关系
- 1、Struts的值栈的核心类是ValueStack接口,主要是其实现类OgnlValueStack
// 忽略其它属性,只看下面两个
protected CompoundRoot root;
// 这个就是常说的contextMap
protected transient Map<String, Object> context;
// ... ...
- 2、Struts的核核心过滤器StrutsPrepareAndExecuteFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
try {
String uri = RequestUtils.getUri(request);
if (this.excludedPatterns != null && this.prepare.isUrlExcluded(request, this.excludedPatterns)) {
LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Checking if {} is a static resource", uri);
boolean handled = this.execute.executeStaticResourceRequest(request, response);
if (!handled) {
LOG.trace("Assuming uri {} as a normal action", uri);
this.prepare.setEncodingAndLocale(request, response);
// 1、值栈是伴随着ActionContext的创建而创建的
this.prepare.createActionContext(request, response);
this.prepare.assignDispatcherToThread();
request = this.prepare.wrapRequest(request);
ActionMapping mapping = this.prepare.findActionMapping(request, response, true);
if (mapping == null) {
LOG.trace("Cannot find mapping for {}, passing to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Found mapping {} for {}", mapping, uri);
this.execute.executeAction(request, response, mapping);
}
}
}
} finally {
this.prepare.cleanupRequest(request);
}
}
- 3、ValueStack伴随ActionContext创建
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
Integer counter = 1;
Integer oldCounter = (Integer)request.getAttribute("__cleanup_recursion_counter");
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
ActionContext ctx;
if (oldContext != null) {
ctx = new ActionContext(new HashMap(oldContext.getContextMap()));
} else {
// 2、创建值栈
ValueStack stack = ((ValueStackFactory)this.dispatcher.getContainer().getInstance(ValueStackFactory.class)).createValueStack();
// 3、创建contextMap,同时也将其压入值栈的context中,毕竟这样才能称其为contextMap
stack.getContext().putAll(this.dispatcher.createContextMap(request, response, (ActionMapping)null));
// 4、将值栈的context作为构造参数传给ActionContext
ctx = new ActionContext(stack.getContext());
}
request.setAttribute("__cleanup_recursion_counter", counter);
// 5、将ActionContext与ThreadLocal绑定
ActionContext.setContext(ctx);
return ctx;
}
/** 第二步这里展开:*/
public ValueStack createValueStack() {
// 创建一个新的值栈
ValueStack stack = new OgnlValueStack(this.xworkConverter, this.compoundRootAccessor, this.textProvider, this.allowStaticMethodAccess);
this.container.inject(stack);
stack.getContext().put("com.opensymphony.xwork2.ActionContext.container", this.container);
return stack;
}
// 如何创建新的值值栈
protected OgnlValueStack(XWorkConverter xworkConverter, CompoundRootAccessor accessor, TextProvider prov, boolean allowStaticAccess) {
// root赋值
this.setRoot(xworkConverter, accessor, new CompoundRoot(), allowStaticAccess);
this.push(prov);
}
protected void setRoot(XWorkConverter xworkConverter, CompoundRootAccessor accessor, CompoundRoot compoundRoot, boolean allowStaticMethodAccess) {
// 初始化root
this.root = compoundRoot;
this.securityMemberAccess = new SecurityMemberAccess(allowStaticMethodAccess);
// 创建默认的Context
this.context = Ognl.createDefaultContext(this.root, accessor, new OgnlTypeConverterWrapper(xworkConverter), this.securityMemberAccess);
// 这里可以看到contexMap维护了一个值栈本身的引用
this.context.put("com.opensymphony.xwork2.util.ValueStack.ValueStack", this);
Ognl.setClassResolver(this.context, accessor);
((OgnlContext)this.context).setTraceEvaluations(false);
((OgnlContext)this.context).setKeepLastEvaluation(false);
}
//
public static Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context) {
// 值栈的contextMap的实际数据结构其实就是OgnlContext
OgnlContext result;
// ... ...
// 这里可以看到contexMap也维护了一个root引用
result.setRoot(root);
return result;
}
- 4、contextMap的创建
public Map<String, Object> createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) {
Map requestMap = new RequestMap(request);
HttpParameters params = HttpParameters.create(request.getParameterMap()).build();
Map session = new SessionMap(request);
Map application = new ApplicationMap(this.servletContext);
// 创建contextMap
Map<String, Object> extraContext = this.createContextMap(requestMap, params, session, application, request, response);
if (mapping != null) {
extraContext.put("struts.actionMapping", mapping);
}
return extraContext;
}
// 在contextMap中添加各种域对象的引用
public HashMap<String, Object> createContextMap(Map requestMap, HttpParameters parameters, Map sessionMap, Map applicationMap, HttpServletRequest request, HttpServletResponse response) {
HashMap<String, Object> extraContext = new HashMap();
extraContext.put("com.opensymphony.xwork2.ActionContext.parameters", parameters);
extraContext.put("com.opensymphony.xwork2.ActionContext.session", sessionMap);
extraContext.put("com.opensymphony.xwork2.ActionContext.application", applicationMap);
extraContext.put("com.opensymphony.xwork2.ActionContext.locale", this.getLocale(request));
extraContext.put("com.opensymphony.xwork2.dispatcher.HttpServletRequest", request);
extraContext.put("com.opensymphony.xwork2.dispatcher.HttpServletResponse", response);
extraContext.put("com.opensymphony.xwork2.dispatcher.ServletContext", this.servletContext);
extraContext.put("request", requestMap);
extraContext.put("session", sessionMap);
extraContext.put("application", applicationMap);
extraContext.put("parameters", parameters);
AttributeMap attrMap = new AttributeMap(extraContext);
extraContext.put("attr", attrMap);
return extraContext;
}
- 5、将值栈的context作为构造参数传给ActionContext
private Map<String, Object> context;
public ActionContext(Map<String, Object> context) {
// 也就是说将值栈(OgnlValueStack)的contextMap属性中的所有东西都给了ActionContext
this.context = context;
}
// 这里说明从ActionContext中获取东西基本也都是从contexMap中获取
public String getName() {
return (String)this.get("com.opensymphony.xwork2.ActionContext.name");
}
public Object get(String key) {
return this.context.get(key);
}
- 6、将ActionContext与ThreadLocal绑定
// 新创建的ActionContext绑定到了ThreadLocal上。
// ThreadLocal的set方法是将ThreadLocal对象和数据对象作为键值对存入线程对象内部的一个Map类型的数据结构里
// 因此,由于ActionContext被绑定在ThreadLocal对象上,所以ActionContext是线程安全的。
static ThreadLocal<ActionContext> actionContext = new ThreadLocal();
public static void setContext(ActionContext context) {
actionContext.set(context);
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
四、为什么请求过来的时候Action类中的属性可能直接获取到值呢?
- 1、当Action接收到请求之后,会先创建一个Action实例,但是这个时候并不会马上调用Action中的方法。
- 2、而是会将Action类中属性先放到值栈(ValueStack)中,这个时候所有的属性没有值或者只有对应类型的默认值。
- 3、这个时候呢,Struts会先调用拦截器链中的各种拦截器(这里可对ValueStack中值做操作,最后在Action中得到操作后的值),调用完拦截器之后;会将值栈(ValueStack)中的属性赋值给Action类中相应的属性。
- 4、然后才会调用Action中的相应方法,这个时候我们就能直接获取到这些值了。
五、总结
- 1、值栈和ActionContext是一起创建的,一次请求创建一次。
- 2、值栈(OgnlValueStack)包含两块:CompoundRoot(ArrayList) root和 Map context(常说的contextMap)。
- 3、contextMap 中 维护了值栈本身和root两个引用。
- 4、因为ActionContext 中维护了contextMap引用,contextMap中以维护了值栈本身的引用,所以ActionContext是间接引用了值栈(直接说ActionContext中维护了值栈的引用并不合适)。
- 5、Action类中的属性可能直接获取到值是因为在请求到达Action之前,Struts已经将值提前存放到值栈中了,并在调用方法之前将这些值赋值给对应的属性。