轉載自:http://www.see-source.com/ 源碼解析網 java
網上對於struts2請求處理流程的講解仍是比較多的,有的仍是很是詳細的,因此這裏我就簡單地將大概流程總結下,有了個大概印象後理解起源碼就會有必定的思路了: web
struts2的請求處理過程其實是在初始化中加載的配置及容器的基礎上,經過請求的url分析出命名空間、action名稱、方法名稱,在利用命名空間檢索出該命名空間下配置的全部antion,在經過action名稱找到具體的action,生成action實例,若是該action上配置了攔截器則依次調用攔截器,以後調用action中方法名稱指定的方法,最後經過配置中的result返回相應視圖。 express
版本:struts2-2.1.6 xwork-2.1.2 apache
下面就經過源碼進行分析下: session
struts2中處理請求是經過過濾器org.apache.struts2.dispatcher.FilterDispatcher的doFilter()方法實現的,以下: app
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; ServletContext servletContext = getServletContext(); String timerKey = "FilterDispatcher_doFilter: "; try { // FIXME: this should be refactored better to not duplicate work with the action invocation ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); ActionContext ctx = new ActionContext(stack.getContext()); ActionContext.setContext(ctx); UtilTimerStack.push(timerKey); request = prepareDispatcherAndWrapRequest(request, response); ActionMapping mapping; try { mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); } catch (Exception ex) { log.error("error getting ActionMapping", ex); dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); return; } if (mapping == null) { // there is no action in this request, should we look for a static resource? String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); } else { // this is a normal request, let it pass through chain.doFilter(request, response); } // The framework did its job here return; } dispatcher.serviceAction(request, response, servletContext, mapping); } finally { try { ActionContextCleanUp.cleanUp(req); } finally { UtilTimerStack.pop(timerKey); } } }
前2句比較簡單,向上轉換req、res爲標準接口HttpServletRequest、HttpServletResponse形式。req是org.apache.catalina.connector.RequestFacade的實例,RequestFacade實現了接口HttpServletRequest,而HttpServletRequest繼承自ServletRequest接口。第3句,得到servletContext即servlet上下文,經過上下文對象能夠訪問web.xml描述文件的初始化參數。第4句定義timerKey變量,值是將當前過濾器的類名和當前方法名拼接起來,看下下面的代碼發現timerKey用於UtilTimerStack.push(timerKey)和UtilTimerStack.pop(timerKey)倆句,其實這倆句並不屬於處理流程的功能代碼,而是性能代碼,主要是監測下doFilter()方法的執行時間,而後經過日誌打印出來,因此這倆句能夠不用理會。ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack()句,首先經過dispatcher.getContainer().getInstance(ValueStackFactory.class)從容器中得到type(類型)爲ValueStackFactory.class的bean,而後調用該bean的createValueStack()建立一個ValueStack實例。ValueStackFactory.class默認狀況下是使用struts-default.xml配置中的 <bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />,即經過上面的容器返回的是個com.opensymphony.xwork2.ognl.OgnlValueStackFactory實例,從名字看出其是個ValueStack工廠,經過調用該工廠的createValueStack()方法返回ValueStack實例。 jsp
ValueStack就是一般所說的"值棧",系統每次請求時都會建立個新的ValueStack,其中會保存着本次請求處理的全部中間數據,如:請求的action實例、各類servlet內置對象(request、response、session、application等)、請求參數等。F5看下定義: ide
public interface ValueStack { public static final String VALUE_STACK = "com.opensymphony.xwork2.util.ValueStack.ValueStack"; public static final String REPORT_ERRORS_ON_NO_PROP = "com.opensymphony.xwork2.util.ValueStack.ReportErrorsOnNoProp"; public abstract Map<String, Object> getContext(); public abstract void setDefaultType(Class defaultType); public abstract void setExprOverrides(Map<Object, Object> overrides); public abstract Map<Object, Object> getExprOverrides(); public abstract CompoundRoot getRoot(); public abstract void setValue(String expr, Object value); public abstract void setValue(String expr, Object value, boolean throwExceptionOnFailure); public abstract String findString(String expr); public abstract Object findValue(String expr); public abstract Object findValue(String expr, Class asType); public abstract Object peek(); public abstract Object pop(); public abstract void push(Object o); public abstract void set(String key, Object o); public abstract int size(); }是個接口,由於ValueStack自己是堆棧,因此咱們看到peek()、pop()、push()等堆棧方法都是有的,其中有個最重要的方法getContext()和getRoot(),getContext()返回的是個Map<String, Object>類型,通常請求中的參數、servlet各類內置對象都是存放在這個Map中。getRoot()返回的就是堆棧數據實際存放的地方,peek()、pop()、push()都是基於其操做的。CompoundRoot是個堆棧類,它繼承了java.util.ArrayList,並以此爲基礎實現了peek()、pop()、push()方法,從而能夠獨立的做爲堆棧使用。這樣的話ValueStack就沒必要在單獨實現堆棧功能,只須要在內部建立個CompoundRoot實例就可,其peek()、pop()、push()方法直接調用CompoundRoot的相應方法便可,實際上struts2中的 ValueStack默認實現類就是這樣作的。另外在這個堆棧中保存的最典型的數據就是action實例。有ognl知識的朋友知道,ognl中基於搜索的有倆個重要對象:上下文和根對象,實際上struts2在利用ognl時,是將getContext()得到的對象做爲ognl的上下文、getRoot()得到的做爲根對象。這就是爲何咱們在經過struts2標籤訪問action中屬性數據時不須要加"#",而訪問request、response、session中數據時須要加"#"的緣由了。由於ognl中訪問根對象是不須要加"#"的,而訪問上下文是須要加"#"的。爲了更好的理解這塊,建議你們先學習下ognl的使用方法及特性。爲了驗證上面的說法,我舉個例子,看下struts2的<s:property value=""/>標籤是如何使用ValueStack的,以及最後如何調用ognl的。
這裏還要事先交代下,在處理流程的後面處理中會將ValueStack(引用)存於倆個地:一個是ActionContext,另外一個是經過request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, valuesStack)將ValueStack存於request中。<s:property value=""/>標籤中使用的ValueStack是經過request得到的。下面看下<s:property value=""/>標籤的源碼,從struts2的標籤訂義文件struts-tags.tld中查到,該標籤對應的類是org.apache.struts2.views.jsp.PropertyTag,以下: 性能
public class PropertyTag extends ComponentTagSupport { private static final long serialVersionUID = 435308349113743852L; private String defaultValue; private String value; private boolean escape = true; private boolean escapeJavaScript = false; public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new Property(stack); } protected void populateParams() { super.populateParams(); Property tag = (Property) component; tag.setDefault(defaultValue); tag.setValue(value); tag.setEscape(escape); tag.setEscapeJavaScript(escapeJavaScript); } public void setDefault(String defaultValue) { this.defaultValue = defaultValue; } public void setEscape(boolean escape) { this.escape = escape; } public void setEscapeJavaScript(boolean escapeJavaScript) { this.escapeJavaScript = escapeJavaScript; } public void setValue(String value) { this.value = value; } }咱們要找下標籤的doStartTag(),這個方法會在標籤被處理前調用。關於標籤這塊,建議你們先學下jsp自定義標籤的使用。
去它的父類ComponentTagSupport中找下: 學習
public abstract class ComponentTagSupport extends StrutsBodyTagSupport { protected Component component; public abstract Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res); public int doEndTag() throws JspException { component.end(pageContext.getOut(), getBody()); component = null; return EVAL_PAGE; } public int doStartTag() throws JspException { component = getBean(getStack(), (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse()); Container container = Dispatcher.getInstance().getContainer(); container.inject(component); populateParams(); boolean evalBody = component.start(pageContext.getOut()); if (evalBody) { return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE; } else { return SKIP_BODY; } } protected void populateParams() { } public Component getComponent() { return component; } }
找到了。doStartTag()方法第1句,調用getBean(),其中有個參數調用了getStack(),實際上這個方法返回的就是我上面說的ValueStack。F5進入:
public class StrutsBodyTagSupport extends BodyTagSupport { private static final long serialVersionUID = -1201668454354226175L; protected ValueStack getStack() { return TagUtils.getStack(pageContext); } . . . //省略 }
該方法是在ComponentTagSupport的父類StrutsBodyTagSupport中。其中只有一句TagUtils.getStack(pageContext),pageContext就是servlet的頁面上下文內置對象,F5進入TagUtils.getStack(pageContext):
public static ValueStack getStack(PageContext pageContext) { HttpServletRequest req = (HttpServletRequest) pageContext.getRequest(); ValueStack stack = (ValueStack) req.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); if (stack == null) { HttpServletResponse res = (HttpServletResponse) pageContext.getResponse(); Dispatcher du = Dispatcher.getInstance(); if (du == null) { throw new ConfigurationException("The Struts dispatcher cannot be found. This is usually caused by "+ "using Struts tags without the associated filter. Struts tags are only usable when the request "+ "has passed through its servlet filter, which initializes the Struts dispatcher needed for this tag."); } stack = du.getContainer().getInstance(ValueStackFactory.class).createValueStack(); Map<String, Object> extraContext = du.createContextMap(new RequestMap(req), req.getParameterMap(), new SessionMap(req), new ApplicationMap(pageContext.getServletContext()), req, res, pageContext.getServletContext()); extraContext.put(ServletActionContext.PAGE_CONTEXT, pageContext); stack.getContext().putAll(extraContext); req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); // also tie this stack/context to the ThreadLocal ActionContext.setContext(new ActionContext(stack.getContext())); } else { // let's make sure that the current page context is in the action context Map<String, Object> context = stack.getContext(); context.put(ServletActionContext.PAGE_CONTEXT, pageContext); AttributeMap attrMap = new AttributeMap(context); context.put("attr", attrMap); } return stack; }
方法第1句,經過頁面上下文對象pageContext得到本次請求的request對象,第2句在經過req.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY)得到ValueStack,由於事先struts2已經經過request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, valuesStack)將ValueStack存於request的ServletActionContext.STRUTS_VALUESTACK_KEY屬性中,因此經過第2句就能夠得到ValueStack,直接return stack返回到ComponentTagSupport類的doStartTag()方法,以下(我在把代碼粘下):
public int doStartTag() throws JspException { component = getBean(getStack(), (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse()); Container container = Dispatcher.getInstance().getContainer(); container.inject(component); populateParams(); boolean evalBody = component.start(pageContext.getOut()); if (evalBody) { return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE; } else { return SKIP_BODY; } }
執行完getStack()接着看getBean()方法,在ComponentTagSupport類中能夠找到,getBean()方法被定義爲抽象方法,因此具體的實現要在其子類PropertyTag中找,F5進入:
public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { return new Property(stack); }
方法只有一句經過new 生成了Property實例,同時將stack做爲構造參數傳進去,這個stack就是咱們上面經過getStack()獲得的ValueStack,執行完後程序會從新回到ComponentTagSupport類的doStartTag()方法,我把代碼在粘下:
public int doStartTag() throws JspException { component = getBean(getStack(), (HttpServletRequest) pageContext.getRequest(), (HttpServletResponse) pageContext.getResponse()); Container container = Dispatcher.getInstance().getContainer(); container.inject(component); populateParams(); boolean evalBody = component.start(pageContext.getOut()); if (evalBody) { return component.usesBody() ? EVAL_BODY_BUFFERED : EVAL_BODY_INCLUDE; } else { return SKIP_BODY; } }
這樣component中實際引用的就是Property實例,第二、3句,調用容器,經過container.inject(component)將Property中須要注入的屬性賦值(帶有@Inject標註的)。populateParams()句將標籤屬性添加到component相應屬性中,如value、escape。核心在下面這句component.start(pageContext.getOut()),經過ognl訪問ValueStack從而得到標籤的value值,F5進入Property的start():
public boolean start(Writer writer) { boolean result = super.start(writer); String actualValue = null; if (value == null) { value = "top"; } else { value = stripExpressionIfAltSyntax(value); } // exception: don't call findString(), since we don't want the // expression parsed in this one case. it really // doesn't make sense, in fact. actualValue = (String) getStack().findValue(value, String.class); try { if (actualValue != null) { writer.write(prepare(actualValue)); } else if (defaultValue != null) { writer.write(prepare(defaultValue)); } } catch (IOException e) { LOG.info("Could not print out value '" + value + "'", e); } return result; }
直接看(String) getStack().findValue(value, String.class) 調用getStack()得到ValueStack,這個ValueStack實在Property實例生成時經過構造方法傳入的。以後調用ValueStack的findValue()方法, 其中參數value就是<s:property value=""/>標籤的value屬性值。咱們在上面說過ValueStack的實現類默認使用的是com.opensymphony.xwork2.ognl.OgnlValueStack類,F5進入其findValue():
public Object findValue(String expr, Class asType) { try { if (expr == null) { return null; } if ((overrides != null) && overrides.containsKey(expr)) { expr = (String) overrides.get(expr); } Object value = ognlUtil.getValue(expr, context, root, asType); if (value != null) { return value; } else { return findInContext(expr); } } catch (OgnlException e) { return findInContext(expr); } catch (Exception e) { logLookupFailure(expr, e); return findInContext(expr); } finally { ReflectionContextState.clear(context); } }
直接看ognlUtil.getValue(expr, context, root, asType)句,能夠看到content、root就是咱們上面說的ValueStack中getContext()和getRoot()方法中對應的屬性值,咱們說過它分別對應ognl中的上下文和根對象。ognlUtil是com.opensymphony.xwork2.ognl.OgnlUtil類的實例,是struts2中用於操做ognl而單獨封裝的管理類, F5進入ognlUtil.getValue()方法:
public Object getValue(String name, Map<String, Object> context, Object root, Class resultType) throws OgnlException { return Ognl.getValue(compile(name), context, root, resultType); }實際上,上面的方法只是將name作處理後直接調用ognl的getValue()方法。context做爲ognl的上下文,root做爲ognl的根對象,name是屬性名。此時的root中就存放着當前action的實例。return返回的值就是<s:property value=""/>標籤最終所得到的值。說道這熟悉ognl的應該已經明白了。