一、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已經將值提前存放到值棧中了,並在調用方法之前將這些值賦值給對應的屬性。