struts2流程介紹
1、struts2流程介紹
首先要知道struts2是在webwork的技術基礎上開發的,採用攔截器的機制來處理使用者請求的全新MVC框架。而webwork是建立在xwork的command模式框架之上的基於web的MVC框架。所以總而言之,無論是struts2還是webwork底層都是xwork。
從其官方網站的介紹來看,XWork不僅提供了一系列基礎構件,其中包括:一個IoC的容器、強大的表示式語言(OGNL)支援、資料型別轉化、資料校驗框架、可插拔的功能模組(外掛模式)及其配置,並且在這一系列的基礎構件之上,實現了一套基於Command設計模式的“事件請求執行框架”。
那麼,XWork作為Struts2所依賴的底層核心,使得Struts2只需要關注與Web容器打交道的部分,而把其餘的工作交給XWork即可。當Struts2收到一個Http請求時,Struts2只需要接收請求引數,交給XWork完成執行序列,當XWork執行完畢後,將結果交還Struts2返回相應的檢視。
我這裡的struts2原始碼是從官網下載的一個最新的struts-2.3.15.1-src.zip,將其解壓即可。裡面的目錄頁檔案非常的多,我們只需要定位到struts-2.3.15.1\src\core\src\main\java\org\apache\struts2檢視原始檔。目錄結構如下圖:
Struts2框架的正常執行,除了佔核心地位的xwork的支援以外,Struts2本身也提供了許多類,這些類被分門別類組織到不同的包中。從原始碼中發現,基本上每一個Struts2類都訪問了WebWork提供的功能,從而也可以看出Struts2與WebWork千絲萬縷的聯絡。但無論如何,Struts2的核心功能比如將請求委託給哪個Action處理都是由xwork完成的,Struts2只是在WebWork的基礎上做了適當的簡化、加強和封裝,並少量保留Struts1.x中的習慣。
以下是包說明:
根目錄下的5個檔案說明:
struts2 架構圖如下圖所示:
依照上圖,我們可以看出一個請求在struts的處理大概有如下步驟:
- 客戶端初始化一個指向Servlet容器(例如Tomcat)的請求;
- 這個請求經過一系列的過濾器(Filter)(這些過濾器中有一個叫做ActionContextCleanUp的可選過濾器,這個過濾器對於Struts2和其他框架的整合很有幫助,例如:SiteMesh Plugin);
- 接著StrutsPrepareAndExecuteFilter被呼叫,執行doFilter()方法,詢問ActionMapper來決定這個請求是否需要呼叫某個Action;
- 如果ActionMapper決定需要呼叫某個Action,StrutsPrepareAndExecuteFilter把請求的處理交給ActionProxy;
- ActionProxy通過Configuration Manager詢問框架的配置檔案,找到需要呼叫的Action類;
- ActionProxy建立一個ActionInvocation的例項。
- ActionInvocation例項使用命名模式來呼叫,在呼叫Action的過程前後,涉及到相關攔截器(Intercepter)的呼叫。
- 一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果。返回結果通常是(但不總是,也可能是另外的一個Action鏈)一個需要被表示的JSP或者FreeMarker的模版。在表示的過程中可以使用Struts2 框架中繼承的標籤。在這個過程中需要涉及到ActionMapper。
2、struts2原始碼分析
首先我們使用struts2框架都會在web.xml中註冊和對映struts2,配置內容如下:
<span style="color:#000000"><code> <span style="color:#006666"><<span style="color:#4f4f4f">filter</span>></span>
<span style="color:#006666"><<span style="color:#4f4f4f">filter-name</span>></span>struts2<span style="color:#006666"></<span style="color:#4f4f4f">filter-name</span>></span>
<span style="color:#006666"><<span style="color:#4f4f4f">filterclass</span>></span>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
<span style="color:#006666"></<span style="color:#4f4f4f">filter-class</span>></span>
<span style="color:#006666"></<span style="color:#4f4f4f">filter</span>></span>
<span style="color:#006666"><<span style="color:#4f4f4f">filter-mapping</span>></span>
<span style="color:#006666"><<span style="color:#4f4f4f">filter-name</span>></span>struts2<span style="color:#006666"></<span style="color:#4f4f4f">filter-name</span>></span>
<span style="color:#006666"><<span style="color:#4f4f4f">url-pattern</span>></span>/*<span style="color:#006666"></<span style="color:#4f4f4f">url-pattern</span>></span>
<span style="color:#006666"></<span style="color:#4f4f4f">filter-mapping</span>></span></code></span>
注:在早期的struts2中,都是使用FilterDispathcer,從Struts 2.1.3開始,它已不推薦使用。升級到StrutsPrepareAndExecuteFilter。
StrutsPrepareAndExecuteFilter中的方法:
web容器一啟動,就會初始化核心過濾器StrutsPrepareAndExecuteFilter,並執行初始化init()方法,初始化方法如下:
<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">init</span>(FilterConfig filterConfig) <span style="color:#000088">throws</span> ServletException {
InitOperations init = <span style="color:#000088">new</span> InitOperations();
Dispatcher dispatcher = <span style="color:#000088">null</span>;
<span style="color:#000088">try</span> {
<span style="color:#880000">//封裝filterConfig,其中有個主要方法getInitParameterNames將引數名字以String格式儲存在List中</span>
FilterHostConfig config = <span style="color:#000088">new</span> FilterHostConfig(filterConfig);
<span style="color:#880000">//初始化struts內部日誌</span>
init.initLogging(config);
<span style="color:#880000">//建立dispatcher ,並初始化</span>
dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
<span style="color:#880000">//初始化類屬性:prepare 、execute</span>
prepare = <span style="color:#000088">new</span> PrepareOperations(filterConfig.getServletContext(), dispatcher);
execute = <span style="color:#000088">new</span> ExecuteOperations(filterConfig.getServletContext(), dispatcher);
<span style="color:#000088">this</span>.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
<span style="color:#880000">//回撥空的postInit方法</span>
postInit(dispatcher, filterConfig);
} <span style="color:#000088">finally</span> {
<span style="color:#000088">if</span> (dispatcher != <span style="color:#000088">null</span>) {
dispatcher.cleanUpAfterInit();
}
init.cleanup();
}
}</code></span>
關於封裝的引數filterConfig,首先看下FilterHostConfig ,原始碼如下:
<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">FilterHostConfig</span> <span style="color:#000088">implements</span> <span style="color:#4f4f4f">HostConfig</span> {
<span style="color:#000088">private</span> FilterConfig config;
<span style="color:#880000">//構造方法</span>
<span style="color:#000088">public</span> <span style="color:#009900">FilterHostConfig</span>(FilterConfig config) {
<span style="color:#000088">this</span>.config = config;
}
<span style="color:#880000">//根據init-param配置的param-name獲取param-value的值 </span>
<span style="color:#000088">public</span> String <span style="color:#009900">getInitParameter</span>(String key) {
<span style="color:#000088">return</span> config.getInitParameter(key);
}
<span style="color:#880000">//返回初始化引數名的迭代器 </span>
<span style="color:#000088">public</span> Iterator<String> <span style="color:#009900">getInitParameterNames</span>() {
<span style="color:#000088">return</span> MakeIterator.convert(config.getInitParameterNames());
}
<span style="color:#880000">//返回Servlet上下文</span>
<span style="color:#000088">public</span> ServletContext <span style="color:#009900">getServletContext</span>() {
<span style="color:#000088">return</span> config.getServletContext();
}
}</code></span>
只有短短的幾行程式碼,getInitParameterNames是這個類的核心,將Filter初始化引數名稱有列舉型別轉為Iterator。此類的主要作為是對filterConfig 封裝。
接下來,看下StrutsPrepareAndExecuteFilter中init方法中dispatcher = init.initDispatcher(config)。這是初始化dispatcher的,是通過init物件的initDispatcher方法來初始化的。init是InitOperations類的物件,我們看看InitOperations中initDispatcher方法:
<span style="color:#000000"><code><span style="color:#000088">public</span> Dispatcher <span style="color:#009900">initDispatcher</span>( HostConfig filterConfig ) {
Dispatcher dispatcher = createDispatcher(filterConfig);
dispatcher.init();
<span style="color:#000088">return</span> dispatcher;
}</code></span>
建立Dispatcher,會讀取 filterConfig 中的配置資訊,將配置資訊解析出來,封裝成為一個Map,然後根絕servlet上下文和引數Map構造Dispatcher :
<span style="color:#000000"><code><span style="color:#000088">private</span> Dispatcher createDispatcher( HostConfig filterConfig ) {
<span style="color:#880000">//存放參數的Map</span>
<span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, <span style="color:#4f4f4f">String</span><span style="color:#4f4f4f">></span> <span style="color:#000088">params</span> <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> HashMap<span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, <span style="color:#4f4f4f">String</span><span style="color:#4f4f4f">></span>();
<span style="color:#880000">//將引數存放到Map</span>
for ( Iterator e <span style="color:#4f4f4f">=</span> filterConfig<span style="color:#4f4f4f">.</span>getInitParameterNames(); e<span style="color:#4f4f4f">.</span>hasNext(); ) {
<span style="color:#4f4f4f">String</span> name <span style="color:#4f4f4f">=</span> (<span style="color:#4f4f4f">String</span>) e<span style="color:#4f4f4f">.</span>next();
<span style="color:#4f4f4f">String</span> value <span style="color:#4f4f4f">=</span> filterConfig<span style="color:#4f4f4f">.</span>getInitParameter(name);
<span style="color:#000088">params</span><span style="color:#4f4f4f">.</span>put(name, value);
}
<span style="color:#880000">//根據servlet上下文和引數Map構造Dispatcher </span>
<span style="color:#000088">return</span> <span style="color:#006666">new</span> Dispatcher(filterConfig<span style="color:#4f4f4f">.</span>getServletContext(), <span style="color:#000088">params</span>);
}</code></span>
這樣dispatcher物件建立完成,接著就是dispatcher物件的初始化,開啟Dispatcher類,看到它的init方法如下:
<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> init() {
<span style="color:#000088">if</span> (configurationManager == <span style="color:#000088">null</span>) {
configurationManager = createConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
}
<span style="color:#000088">try</span> {
init_FileManager();
<span style="color:#880000">//載入org/apache/struts2/default.properties</span>
init_DefaultProperties(); <span style="color:#880000">// [1]</span>
<span style="color:#880000">//載入struts-default.xml,struts-plugin.xml,struts.xml</span>
init_TraditionalXmlConfigurations(); <span style="color:#880000">// [2]</span>
init_LegacyStrutsProperties(); <span style="color:#880000">// [3]</span>
<span style="color:#880000">//使用者自己實現的ConfigurationProviders類 </span>
init_CustomConfigurationProviders(); <span style="color:#880000">// [5]</span>
<span style="color:#880000">//Filter的初始化引數 </span>
init_FilterInitParameters() ; <span style="color:#880000">// [6]</span>
init_AliasStandardObjects() ; <span style="color:#880000">// [7]</span>
Container <span style="color:#000088">container</span> = init_PreloadConfiguration();
<span style="color:#000088">container</span>.inject(<span style="color:#000088">this</span>);
init_CheckWebLogicWorkaround(<span style="color:#000088">container</span>);
<span style="color:#000088">if</span> (!dispatcherListeners.isEmpty()) {
<span style="color:#000088">for</span> (DispatcherListener l : dispatcherListeners) {
l.dispatcherInitialized(<span style="color:#000088">this</span>);
}
}
} <span style="color:#000088">catch</span> (Exception ex) {
<span style="color:#000088">if</span> (LOG.isErrorEnabled())
LOG.error(<span style="color:#009900">"Dispatcher initialization failed"</span>, ex);
<span style="color:#000088">throw</span> <span style="color:#000088">new</span> StrutsException(ex);
}
}</code></span>
這裡主要是載入一些配置檔案的,將按照順序逐一載入:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……關於檔案是如何載入的,大家可以自己取看原始檔,主要是由xwork核心類載入的,程式碼在xwork-core\src\main\java\com\opensymphony\xwork2\config\providers包裡面。
現在,我們回到StrutsPrepareAndExecuteFilter類中,剛才我們分析了StrutsPrepareAndExecuteFilter類的init方法,該方法在web容器一啟動就會呼叫的。當用戶訪問某個action的時候,首先呼叫核心過濾器StrutsPrepareAndExecuteFilter的doFilter方法,該方法內容如下:
<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">doFilter</span>(ServletRequest req, ServletResponse res, FilterChain chain) <span style="color:#000088">throws</span> IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
<span style="color:#000088">try</span> {
<span style="color:#880000">//設定編碼和國際化</span>
prepare.setEncodingAndLocale(request, response);
<span style="color:#880000">//建立action上下文</span>
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
<span style="color:#000088">if</span> (excludedPatterns != <span style="color:#000088">null</span> && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} <span style="color:#000088">else</span> {
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, <span style="color:#000088">true</span>);
<span style="color:#880000">//如果mapping為空,則認為不是呼叫action,會呼叫下一個過濾器鏈,直到獲取到mapping才呼叫action</span>
<span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span>) {
<span style="color:#000088">boolean</span> handled = execute.executeStaticResourceRequest(request, response);
<span style="color:#000088">if</span> (!handled) {
chain.doFilter(request, response);
}
} <span style="color:#000088">else</span> {
<span style="color:#880000">//執行action</span>
execute.executeAction(request, response, mapping);
}
}
} <span style="color:#000088">finally</span> {
prepare.cleanupRequest(request);
}
}</code></span>
下面對doFilter方法中的重點部分一一講解:
- prepare.setEncodingAndLocale(request, response)
第8行是呼叫prepare物件的setEncodingAndLocale方法,prepare是PrepareOperations類的物件,PrepareOperations類是用來做請求準備工作的。我們看下PrepareOperations類中的setEncodingAndLocale方法:
<span style="color:#000000"><code><span style="color:#000088">public</span> void setEncodingAndLocale(HttpServletRequest <span style="color:#4f4f4f">request</span>, HttpServletResponse <span style="color:#4f4f4f">response</span>) {
dispatcher.prepare(<span style="color:#4f4f4f">request</span>, <span style="color:#4f4f4f">response</span>);
}</code></span>
在這方法裡面我們可以看到它只是呼叫了dispatcher的prepare方法而已,下面我們看看dispatcher的prepare方法:
<span style="color:#000000"><code>public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = <span style="color:#006666">null</span>;
<span style="color:#000088">if</span> (defaultEncoding != <span style="color:#006666">null</span>) {
encoding = defaultEncoding;
}
<span style="color:#008800">//</span> check <span style="color:#000088">for</span> Ajax request to use UTF-<span style="color:#006666">8</span> encoding strictly <span style="color:#009900">http</span>:<span style="color:#008800">//</span>www.w3.org<span style="color:#008800">/TR/XMLHttpRequest/</span><span style="color:#880000">#the-send-method</span>
<span style="color:#000088">if</span> (<span style="color:#009900">"XMLHttpRequest"</span>.equals(request.getHeader(<span style="color:#009900">"X-Requested-With"</span>))) {
encoding = <span style="color:#009900">"UTF-8"</span>;
}
Locale locale = <span style="color:#006666">null</span>;
<span style="color:#000088">if</span> (defaultLocale != <span style="color:#006666">null</span>) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}
<span style="color:#000088">if</span> (encoding != <span style="color:#006666">null</span>) {
applyEncoding(request, encoding);
}
<span style="color:#000088">if</span> (locale != <span style="color:#006666">null</span>) {
response.setLocale(locale);
}
<span style="color:#000088">if</span> (paramsWorkaroundEnabled) {
request.getParameter(<span style="color:#009900">"foo"</span>); <span style="color:#008800">//</span> simply read any parameter (existing <span style="color:#000088">or</span> <span style="color:#000088">not</span>) to <span style="color:#009900">"prime"</span> the request
}
}</code></span>
我們可以看到該方法只是簡單的設定了encoding 和locale ,做的只是一些輔助的工作。
- prepare.createActionContext(request, response)
我們回到StrutsPrepareAndExecuteFilter的doFilter方法,看到第10行程式碼:prepare.createActionContext(request, response);這是action上下文的建立。ActionContext是一個容器,這個容器主要儲存request、session、application、parameters等相關信 息。ActionContext是一個執行緒的本地變數,這意味著不同的action之間不會共享ActionContext,所以也不用考慮執行緒安全問 題。其實質是一個Map,key是標示request、session、……的字串,值是其對應的物件,我們可以看到com.opensymphony.xwork2.ActionContext類中時如下定義的:
<span style="color:#000000"><code> <span style="color:#000088">static</span> ThreadLocal<ActionContext> actionContext = <span style="color:#000088">new</span> ThreadLocal<ActionContext>();</code></span>
我們看下PrepareOperations類的createActionContext方法:
<span style="color:#000000"><code> <span style="color:#880000">/**
* Creates the action context and initializes the thread local
*/</span>
<span style="color:#000088">public</span> ActionContext <span style="color:#009900">createActionContext</span>(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
Integer counter = <span style="color:#006666">1</span>;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
<span style="color:#000088">if</span> (oldCounter != <span style="color:#000088">null</span>) {
counter = oldCounter + <span style="color:#006666">1</span>;
}
<span style="color:#880000">//此處是從ThreadLocal中獲取此ActionContext變數</span>
ActionContext oldContext = ActionContext.getContext();
<span style="color:#000088">if</span> (oldContext != <span style="color:#000088">null</span>) {
<span style="color:#880000">// detected existing context, so we are probably in a forward</span>
ctx = <span style="color:#000088">new</span> ActionContext(<span style="color:#000088">new</span> HashMap<String, Object>(oldContext.getContextMap()));
} <span style="color:#000088">else</span> {
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
stack.getContext().putAll(dispatcher.createContextMap(request, response, <span style="color:#000088">null</span>, servletContext));
<span style="color:#880000">//stack.getContext()返回的是一個Map<String,Object>,根據此Map構造一個ActionContext</span>
ctx = <span style="color:#000088">new</span> ActionContext(stack.getContext());
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
<span style="color:#880000">//將ActionContext存到ThreadLocal </span>
ActionContext.setContext(ctx);
<span style="color:#000088">return</span> ctx;
}</code></span>
上面第18行程式碼中dispatcher.createContextMap,如何封裝相關引數:
<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>,Object<span style="color:#4f4f4f">></span> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping, ServletContext context) {
<span style="color:#880000">// request map wrapping the http request objects</span>
<span style="color:#4f4f4f">Map</span> requestMap <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> RequestMap(request);
<span style="color:#880000">// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately</span>
<span style="color:#4f4f4f">Map</span> <span style="color:#000088">params</span> <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> HashMap(request<span style="color:#4f4f4f">.</span>getParameterMap());
<span style="color:#880000">// session map wrapping the http session</span>
<span style="color:#4f4f4f">Map</span> session <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> SessionMap(request);
<span style="color:#880000">// application map wrapping the ServletContext</span>
<span style="color:#4f4f4f">Map</span> application <span style="color:#4f4f4f">=</span> <span style="color:#006666">new</span> ApplicationMap(context);
<span style="color:#880000">//requestMap、params、session等Map封裝成為一個上下文Map </span>
<span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>,Object<span style="color:#4f4f4f">></span> extraContext <span style="color:#4f4f4f">=</span> createContextMap(requestMap, <span style="color:#000088">params</span>, session, application, request, response, context);
<span style="color:#000088">if</span> (mapping <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
extraContext<span style="color:#4f4f4f">.</span>put(ServletActionContext<span style="color:#4f4f4f">.</span>ACTION_MAPPING, mapping);
}
<span style="color:#000088">return</span> extraContext;
}</code></span>
- request = prepare.wrapRequest(request)
我們再次回到StrutsPrepareAndExecuteFilter的doFilter方法中,看到第15行:request = prepare.wrapRequest(request);這一句是對request進行包裝的,我們看下prepare的wrapRequest方法:
<span style="color:#000000"><code><span style="color:#000088">public</span> HttpServletRequest <span style="color:#009900">wrapRequest</span>(HttpServletRequest oldRequest) <span style="color:#000088">throws</span> ServletException {
HttpServletRequest request = oldRequest;
<span style="color:#000088">try</span> {
<span style="color:#880000">// Wrap request first, just in case it is multipart/form-data</span>
<span style="color:#880000">// parameters might not be accessible through before encoding (ww-1278)</span>
request = dispatcher.wrapRequest(request, servletContext);
} <span style="color:#000088">catch</span> (IOException e) {
<span style="color:#000088">throw</span> <span style="color:#000088">new</span> ServletException(<span style="color:#009900">"Could not wrap servlet request with MultipartRequestWrapper!"</span>, e);
}
<span style="color:#000088">return</span> request;
}</code></span>
由第6行我們可以看到它裡面呼叫的是dispatcher的wrapRequest方法,並且將servletContext物件也傳進去了,我們看下dispatcher的wrapRequest:
此次包裝根據請求內容的型別不同,返回不同的物件,如果為multipart/form-data型別,則返回MultiPartRequestWrapper型別的物件,該物件服務於檔案上傳,否則返回StrutsRequestWrapper型別的物件,MultiPartRequestWrapper是StrutsRequestWrapper的子類,而這兩個類都是HttpServletRequest介面的實現。
- ActionMapping mapping = prepare.findActionMapping(request, response, true)
包裝request後,通過ActionMapper的getMapping()方法得到請求的Action,Action的配置資訊儲存在ActionMapping物件中,如StrutsPrepareAndExecuteFilter的doFilter方法中第16行:ActionMapping mapping = prepare.findActionMapping(request, response, true);我們找到prepare物件的findActionMapping方法:
<span style="color:#000000"><code> <span style="color:#000088">public</span> ActionMapping <span style="color:#009900">findActionMapping</span>(HttpServletRequest request, HttpServletResponse response, <span style="color:#000088">boolean</span> forceLookup) {
<span style="color:#880000">//首先從request物件中取mapping物件,看是否存在</span>
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
<span style="color:#880000">//不存在就建立一個</span>
<span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span> || forceLookup) {
<span style="color:#000088">try</span> {
<span style="color:#880000">//首先建立ActionMapper物件,通過ActionMapper物件建立mapping物件</span>
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
<span style="color:#000088">if</span> (mapping != <span style="color:#000088">null</span>) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} <span style="color:#000088">catch</span> (Exception ex) {
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}
<span style="color:#000088">return</span> mapping;
}</code></span>
下面是ActionMapper介面的實現類DefaultActionMapper的getMapping()方法的原始碼:
<span style="color:#000000"><code> <span style="color:#000088">public</span> ActionMapping <span style="color:#009900">getMapping</span>(HttpServletRequest request, ConfigurationManager configManager) {
ActionMapping mapping = <span style="color:#000088">new</span> ActionMapping();
<span style="color:#880000">//獲得請求的uri,即請求路徑URL中工程名以後的部分,如/userAction.action</span>
String uri = getUri(request);
<span style="color:#880000">//修正url的帶;jsessionid 時找不到的bug</span>
<span style="color:#000088">int</span> indexOfSemicolon = uri.indexOf(<span style="color:#009900">";"</span>);
uri = (indexOfSemicolon > -<span style="color:#006666">1</span>) ? uri.substring(<span style="color:#006666">0</span>, indexOfSemicolon) : uri;
<span style="color:#880000">//刪除副檔名,如.action或者.do</span>
uri = dropExtension(uri, mapping);
<span style="color:#000088">if</span> (uri == <span style="color:#000088">null</span>) {
<span style="color:#000088">return</span> <span style="color:#000088">null</span>;
}
<span style="color:#880000">//從uri中分離得到請求的action名、名稱空間。</span>
parseNameAndNamespace(uri, mapping, configManager);
<span style="color:#880000">//處理特殊的請求引數,將這些引數儲存在一個HashSet中</span>
handleSpecialParameters(request, mapping);
<span style="color:#880000">//如果允許動態方法呼叫,即形如/userAction!getAll.action的請求,分離action名和方法名</span>
<span style="color:#000088">return</span> parseActionName(mapping);
}</code></span>
可以看到,該方法從請求的uri中分離出name和namespace並存放在mapping中。並對請求action名解析,分離出action和方法名。
5. execute.executeAction(request, response, mapping)
上面我們分析完了mapping的獲取,繼續看doFilter方法:
<span style="color:#000000"><code><span style="color:#880000">//如果mapping為空,則認為不是呼叫action,會呼叫下一個過濾器鏈,直到獲取到mapping才呼叫action</span>
<span style="color:#000088">if</span> (mapping == <span style="color:#000088">null</span>) {
<span style="color:#000088">boolean</span> handled = execute.executeStaticResourceRequest(request, response);
<span style="color:#000088">if</span> (!handled) {
chain.doFilter(request, response);
}
} <span style="color:#000088">else</span> {
<span style="color:#880000">//執行action</span>
execute.executeAction(request, response, mapping);
}</code></span>
如果mapping物件不為空,則會執行action,我們看到上面程式碼第9行:execute是ExecuteOperations類的物件,ExecuteOperations類在包org.apache.struts2.dispatcher.ng下面,我們找到它裡面的executeAction方法:
<span style="color:#000000"><code> <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">executeAction</span>(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) <span style="color:#000088">throws</span> ServletException {
dispatcher.serviceAction(request, response, servletContext, mapping);
}</code></span>
我們可以看到它裡面只是簡單的呼叫了dispatcher的serviceAction方法:我們找到dispatcher的serviceAction方法:
<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#006666">void</span> serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {
<span style="color:#880000">//封轉上下文環境,主要將requestMap、params、session等Map封裝成為一個上下文Map</span>
<span style="color:#4f4f4f">Map</span><span style="color:#4f4f4f"><</span><span style="color:#4f4f4f">String</span>, Object<span style="color:#4f4f4f">></span> extraContext <span style="color:#4f4f4f">=</span> createContextMap(request, response, mapping, context);
<span style="color:#880000">// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action</span>
ValueStack <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">=</span> (ValueStack) request<span style="color:#4f4f4f">.</span>getAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY);
boolean nullStack <span style="color:#4f4f4f">=</span> <span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">==</span> <span style="color:#4f4f4f">null</span>;
<span style="color:#000088">if</span> (nullStack) {
ActionContext ctx <span style="color:#4f4f4f">=</span> ActionContext<span style="color:#4f4f4f">.</span>getContext();
<span style="color:#000088">if</span> (ctx <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
<span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">=</span> ctx<span style="color:#4f4f4f">.</span>getValueStack();
}
}
<span style="color:#000088">if</span> (<span style="color:#4f4f4f">stack</span> <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
extraContext<span style="color:#4f4f4f">.</span>put(ActionContext<span style="color:#4f4f4f">.</span>VALUE_STACK, valueStackFactory<span style="color:#4f4f4f">.</span>createValueStack(<span style="color:#4f4f4f">stack</span>));
}
<span style="color:#4f4f4f">String</span> timerKey <span style="color:#4f4f4f">=</span> <span style="color:#009900">"Handling request from Dispatcher"</span>;
try {
UtilTimerStack<span style="color:#4f4f4f">.</span>push(timerKey);
<span style="color:#4f4f4f">String</span> namespace <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getNamespace();<span style="color:#880000">//從mapping物件獲取名稱空間</span>
<span style="color:#4f4f4f">String</span> name <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getName(); <span style="color:#880000">//獲取請求的action名</span>
<span style="color:#4f4f4f">String</span> method <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getMethod(); <span style="color:#880000">//獲取請求方法</span>
<span style="color:#880000">//得到配置物件</span>
Configuration config <span style="color:#4f4f4f">=</span> configurationManager<span style="color:#4f4f4f">.</span>getConfiguration();
<span style="color:#880000">//根據執行上下文引數,名稱空間,名稱等建立使用者自定義Action的代理物件 </span>
ActionProxy proxy <span style="color:#4f4f4f">=</span> config<span style="color:#4f4f4f">.</span>getContainer()<span style="color:#4f4f4f">.</span>getInstance(ActionProxyFactory<span style="color:#4f4f4f">.</span>class)<span style="color:#4f4f4f">.</span>createActionProxy(
namespace, name, method, extraContext, <span style="color:#006666">true</span>, <span style="color:#006666">false</span>);
request<span style="color:#4f4f4f">.</span>setAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY, proxy<span style="color:#4f4f4f">.</span>getInvocation()<span style="color:#4f4f4f">.</span>getStack());
<span style="color:#880000">// if the ActionMapping says to go straight to a result, do it!</span>
<span style="color:#880000">//如果配置檔案中執行的這個action配置了result,就直接轉到result</span>
<span style="color:#000088">if</span> (mapping<span style="color:#4f4f4f">.</span>getResult() <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
Result result <span style="color:#4f4f4f">=</span> mapping<span style="color:#4f4f4f">.</span>getResult();
result<span style="color:#4f4f4f">.</span>execute(proxy<span style="color:#4f4f4f">.</span>getInvocation());
} <span style="color:#000088">else</span> {
proxy<span style="color:#4f4f4f">.</span>execute();
}
<span style="color:#880000">// If there was a previous value stack then set it back onto the request</span>
<span style="color:#000088">if</span> (<span style="color:#4f4f4f">!</span>nullStack) {
request<span style="color:#4f4f4f">.</span>setAttribute(ServletActionContext<span style="color:#4f4f4f">.</span>STRUTS_VALUESTACK_KEY, <span style="color:#4f4f4f">stack</span>);
}
} catch (ConfigurationException e) {
<span style="color:#880000">// WW-2874 Only log error if in devMode</span>
<span style="color:#000088">if</span> (devMode) {
<span style="color:#4f4f4f">String</span> reqStr <span style="color:#4f4f4f">=</span> request<span style="color:#4f4f4f">.</span>getRequestURI();
<span style="color:#000088">if</span> (request<span style="color:#4f4f4f">.</span>getQueryString() <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
reqStr <span style="color:#4f4f4f">=</span> reqStr <span style="color:#4f4f4f">+</span> <span style="color:#009900">"?"</span> <span style="color:#4f4f4f">+</span> request<span style="color:#4f4f4f">.</span>getQueryString();
}
<span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>error(<span style="color:#009900">"Could not find action or result\n"</span> <span style="color:#4f4f4f">+</span> reqStr, e);
} <span style="color:#000088">else</span> {
<span style="color:#000088">if</span> (<span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>isWarnEnabled()) {
<span style="color:#000088">LOG</span><span style="color:#4f4f4f">.</span>warn(<span style="color:#009900">"Could not find action or result"</span>, e);
}
}
sendError(request, response, context, HttpServletResponse<span style="color:#4f4f4f">.</span>SC_NOT_FOUND, e);
} catch (Exception e) {
<span style="color:#000088">if</span> (handleException <span style="color:#4f4f4f">||</span> devMode) {
sendError(request, response, context, HttpServletResponse<span style="color:#4f4f4f">.</span>SC_INTERNAL_SERVER_ERROR, e);
} <span style="color:#000088">else</span> {
throw <span style="color:#006666">new</span> ServletException(e);
}
} finally {
UtilTimerStack<span style="color:#4f4f4f">.</span>pop(timerKey);
}
}</code></span>
核心程式碼就是根據前面的請求準備工作,為使用者建立ActionProxy物件,執行相應的action,若配置有執行結果,那麼返回執行結果。