struts源代碼閱讀(struts 的執行)

第二篇 struts的執行(struts-1.1版)

本篇詳細介紹struts在初始化之後是如何處理一個請求,並返回數據的。這裏最核心的類是
RequestProcessor以及RequestUtils。RequestProcessor類通過RequestDispatcher實現頁面的跳轉,
而RequestProcessor負責處理request中傳來的請求信息,存放到FormBeanConfig中,以及對要跳轉的
url進行處理。

struts 在初始化完成之後,會根據請求調用doGet(...)或者doPost(...)方法,這兩個方法直接
調用process(request, response)方法。process(...)方法首先判斷當前的request屬於
哪一個ModuleConfig,然後生成與這個ModuleConifg相對應的RequestProcessor,最後調用這個
RequestProcessor的process(...)方法,執行request的請求。

一、RequestUtils.selectModule(String prefix, HttpServletRequest,ServletContext)方法:
這個方法,根據prefix,從ServletContext中選擇相應的ModuleConfig,然後把這個ModuleConfig
保存到request中。ServletContext對應的key值爲Globals.MODULE_KEY + prefix,保存到request
中使用的key值爲Globals.MODULE_KEY。如果在ServletContext中不存在這樣的一個ModuleConfig,
那麼調用request.removeAttribute(Globals.MODULE_KEY)方法。然後以同樣的方法查找、保存
MessageResources對象。當prefix爲空時,會調用下面的方法選擇ModuleConfig。

二、RequestUtils.selectModule(HttpServletRequest, ServletContext)
這個方發首先使用getModuleName(HttpServletRequest, ServletContext)獲取相應的path,然後
通過調用getModuleName(String matchPath, ServletContext)獲取相應的prefix。以這prefix爲
參數調用(一)中的selectModule(...)選擇ModuleConfig。
獲取path的過程爲:首先從request中查找名稱爲
INCLUDE_SERVLET_PATH(javax.servlet.include.servlet_path)的屬性,如果爲空,就調用
request.getServletPath()方法獲取servletPath。

獲取prefix的過程爲:它首先調用getModulePrefixes(ServletContext),獲取所有已存在的
module前綴。 然後通過分析上面獲取的path來判斷當前的url屬於哪一個module,方法是截取
path中最後一個"/"字符前面的字符串,然後與有上面方法中獲取的prefixes[]逐個對比,如果
prefixes[]中存在這樣的一個值,則返回這個截取的字符串,否則繼續截取path最後面的"/"前面
的字符串,然後對比。如果始終找不到,則返回""。

getModulePrefixes(ServletContext)的執行過程是:首先通過
context.getAttribute(PREFIXES_KEY)查找是否存在這樣的一個保存所有的prefix的string array,
如果存在,就說明已經解析過一次了,就直接返回這個string array,否則遍歷ServletContext中
所有的attribute name,查找以MODULE_KEY(org.apache.struts.action.MODULE)開頭的
atrribute name,然後截取這個atrribute name中MODULE_KEY後面的字符串,作爲一個prefix. 最
後把通過這個這個方式獲取的所有的prefix作爲一個ArrayList存儲到ServletContext中,屬性key
值爲PREFIXES_KEY(org.apache.struts.util.PREFIXES),這樣下次再次查找的時候就可以直接從這
個attribute中獲取了。

三、getModuleConfig(HttpServletRequest)
這個方法就是獲取上面的selectModule(...)方法所得到的ModuleConfig。如果找不到這樣的
ModuleConfig,那麼就把ServletContext中缺省的ModuleConfig返回(調用
getServletContext().getAttribute(Globals.MODULE_KEY))

四、getRequestProcessor(ModuleConfig config)
這個方法從根據ModuleConfig的prefix作爲key,從ServletContext中獲取RequestProcessor。這
個key值爲Globals.REQUEST_PROCESSOR_KEY + config.getPrefix()。

如果ServletContext中不存在這樣的一個RequestProcessor,那麼就生成一個新的
RequestProcessor的實例,完成初始化(保存ActionServlet以及ModuleConfig到這個新的實例中)後
將其保存到ServletContext中。

五、RequestProcessor.process(HttpServletRequest, HttpServletResponse)
這是真正執行HttpServletRequst請求的方法。

這個方法首先判斷但前的HttpServletRequest是否是Multipart類型的request,也就是說當前
的request是否有字段是"file"類型。這樣做的原因是這種類型的request中getParameter()等
相類似的方法都是無法執行的,所以要區分對待。如果是Multipart類型的,那麼把這個request包
裝成MultipartRequestWrapper類。

MultipartRequestWrapper 與 HttpServletRequest不同的就是重新實現了
setParameter(String name, String value),getParameter(String name),getParameterNames()
以及getParameterValues(String name)方法。
這些方法的思想是所有的變量名、值對都保存到一個HashMap裏:key爲變量名,value爲變量值,但
是注意value都是String[]格式的。當有一個新的值加入的時候,通過setParameter(...)
方法,把值加到數組的最後。在setParameter(...)中有一個比較少見的保存值的方法,記錄如下:
public void setParameter(String name, String value) {
String[] mValue = (String[]) parameters.get(name);
if (mValue == null) {
mValue = new String[0];
}
String[] newValue = new String[mValue.length + 1];
System.arraycopy(mValue, 0, newValue, 0, mValue.length);
newValue[mValue.length] = value;

parameters.put(name, newValue);
}

然後是調用processPath(HttpServletRequest, HttpServletResponse)獲取地址,以這個地址作爲
從ModuleConfig獲取ActionMapping的Key。這裏首先查詢
request.getAttribute(INCLUDE_PATH_INFO)中是否有這樣的一個值,如果爲空就調用
request.getPathInfo()方法獲取path。如果這樣還是獲取不了,就從
request.getAttribute(INCLUDE_SERVLET_PATH)方法中獲取path,找不到就使用
request.getServletPath()得到的path進行分析。分析過程如下:

然後如果這個path不是屬於當前的ModuleConfig的話,直接返回null。截取prefix後面的字符串,
如果這個字符串以.XX結尾,那麼就截取"."前面的字符,比如
http://localhost:8080/servlet/where/go.do。where爲module的名稱(prefix),那麼我們獲取的
path值爲/go。

然後是通過processLocale(HttpServletRequest ,HttpServletResponse)爲當前用戶設定一個Local
對象,它查找的順序是:moduleConfig.getControllerConfig().getLocale(),
session.getAttribute(Globals.LOCALE_KEY),request.getLocale()。你可以在config XML文件
中通過<controller><set-property..../></controller>執行相關的定義。

調用processContent(HttpServletRequest, HttpServletResponse)爲response設定contentType。
這個contentType是從moduleConfig.getControllerConfig().getContentType()獲取的。你可以
在config XML文件中通過<controller><set-property..../></controller>執行相關的定義。

通過processNoCache(HttpServletRequest, HttpServletResponse)方法設置response的緩存。
你可以在config XML文件中通過<controller><set-property..../></controller>執行相關的定義。

processPreprocess(HttpServletRequest, HttpServletResponse)預處理request,這個方法是預
留的,用戶可以根據自己的需要加一些預處理的程序。

通過processMapping(HttpServletRequest, HttpServletResponse, String path)以
processPath(...)的返回值爲key從ModuleConfig中查找ActionMapping 對象。如果找到了,那麼保
存這個Mapping到request中,key值爲Globals.MAPPING_KEY。如果不存在,那麼遍歷ModuleConfig
中所有的ActionMapping,查找哪一個是缺省的ActionMapping。然後把它保存到request中,key值
爲Globals.MAPPING_KEY。

通過processRoles(HttpServletRequest,HttpServletResponse,ActionMapping)檢查當前用戶是
否有權限執行這個請求。如果request.isUserInRole(roles[i])返回true,則代表有。

通過processActionForm(HttpServletRequest, HttpServletResponse, ActionMapping)生成一個
ActionForm,然後根據ActionMapping所屬的scope,保存到request或者session中。key值爲這個
ActionMapping中的attribute屬性。ActionForm是通過RequestUtils.createActionForm(...)方法
獲取的,在下一篇將會對這個方法進行詳細的說明。這裏只說明ActionForm與FormBeanConfig以及
FormPropertyConfig之間的區別。每個FormPropertyConfig代表Form表單的一個字段,表單中的所
有FormPropertyConfig以一個HashMap保存到FormBeanConfig中。而FormBeanConfig是在
Actionservet初始化的時候生成的:
<form-beans>
<form-bean name="一個名稱,作爲action選擇的key" type="實際對應的ActionForm類"/>
<form-beans>
名稱用來在MoudleConfig中的formBeans HashMap中查找相應的FormBeanConfig,而FormBeanConfig
中有一個type,它保存了上面XML文件中定義的值,用來生成ActionForm。

通過processPopulate(HttpServletRequest, HttpServletResponse, ActionForm,
ActionMapping)方法初始化ActionForm 以及 FormBean,這個方法首先會設定這個ActionForm所屬
於的ActionServlet,然後對這個ActionForm進行初始化。判斷當前的reqest是否Multipart類型,
如果是就把相應的MultipartClass類全名保存到request中,key值爲Globals.MULTIPART_KEY。調
用RequestUtils.populate(...)對request中的參數進行處理,並保存到相應的FormBean中。在下
一篇將會對這個方法進行詳細的說明。最後根據request的parameter判斷當前的請求是否是被取
消,然後把相關信息保存到request中:
if ((request.getParameter(Constants.CANCEL_PROPERTY) != null) ||
(request.getParameter(Constants.CANCEL_PROPERTY_X) != null)) {
request.setAttribute(Globals.CANCEL_KEY, Boolean.TRUE);
}

六、下面說明這個request是如何真正被處理的,下面的方法都是在RequestProcessor中定義。

通過processValidate(HttpServletRequest, HttpServletResponse, ActionForm,
ActionMapping)判斷request中的parameter是否合法。如果合法就返回true,否則取消這次請求,
並且返回到指定的輸入頁面。它判斷合法的過程是:如果這次的請求被取消,那麼直接返回true;
如果沒有要求對request中的parameter進行合法性檢驗,也直接返回true;最後通過調用
form.validate(mapping, request)執行檢驗,如果返回的ActionErrors爲null,那麼代表通過
檢驗,返回true,否則取消這次request。取消的過程是:如果這個請求是Multipart類型的,那麼
要對這個MultipartRequestHandler進行回滾;而後,獲取當前的ActionMapping的input值,即在
config XML 中<action.../>定義的input屬性。如果ActionMapping沒有這個input值,那麼調用
response.sendError(...)方法,然後返回false;如果存在,就保存驗證出錯信息到request中,
key爲Globals.ERROR_KEY,跳轉到input所指定的地址中。

processForward(HttpServletRequest, HttpServletResponse, ActionMapping)以及
processInclude(HttpServletRequest, HttpServletResponse, ActionMapping)分別通過執行
RequestDispatcher.forward(request, response) 以及 RequestDispatcher.include(request,
response)實現對頁面的跳轉。

但是如果當前的請求還有其它操作要執行,那麼ActionMapping中的include或者forward屬性值就
會爲空。這時需要通過調用processActionCreate(HttpServletRequest, HttpServletResponse,
ActionMapping)方法根據config XML文件的配置信息生成一個Action類,然後通過
processActionPerform(...)調用生成的Action中的execute(...)方法。最後根據execute(...)
方法返回的ActionForword類,執行跳轉。在整個過程中有一個url的計算方法,這個將在下一篇
中說明。

七、對ActionMaping結構的說明:
在ActionConfig爲保存Moudle Action屬性的一個javabean,它有以下屬性:
* boolean configured 這個對象的所有屬性是否已經被配置完。如果已經完成,那麼在改變其中
任何屬性都會拋出IllegalStateException("Configuration is frozen")
* ModuleConfig moduleConfig 本ActionConfig類所屬於的ModuleConfig。
* String attribute request範圍 或者 session範圍 的屬性名稱,我們將通過這個屬性名稱來
獲取相應的form bean,注意,這個屬性與form bean中定義的名稱是不一樣的。注意以下的方
法:
public String getAttribute() {
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
* String forward 調用RequestDispatcher.forward()方法時所需要的上下文相關的地址。
* String include 調用RequestDispatcher.include()方法時所需要的上下文相關的地址。
* String type Action類的類全名,如果上面的兩個屬性都沒有定義的話,就會用它來處理
request。
* String input 如果輸入數據沒有通過驗證,將會以這個值爲地址,跳轉到相應的頁面。
* String multipartClass MultipartRequestHandler實現類的全名
* String name 與本類相關的 form bean 的名稱。
* String parameter 用於struts的擴展,存儲其它的配置信息。struts自己不會用到這個屬性。
* String path 上下文相關的地址,這個地址爲下一個將會進入的地址,這個地址必須要以"/"
開頭。如果使用了extension mapping的話,這個地址將不會帶有擴展名。
* String roles 一個以","分隔的角色名稱。這些角色將會有權限訪問這個request。
* String scope 缺省爲session。
* String prefix,String suffix後綴和前綴。
* boolean unknown 標誌當前的ActionConfig類是否是爲了未知的request path而準備的,
ActionMappings將會根據這個標誌返回這個ActionConfig。
* boolean validate 是否調用validate()方法進行數據校驗。

ActionMapping 繼承自 ActionConfig,它增加了從ModuleConfig查找ActionForword的方法。同
時它還提供了從ModuleConfig查找ExceptionConfig的方法,如果找不到,會通過
type.getSuperclass()方法,根據父類的類名稱繼續查找,直到最後。

八、關於request.getServletPath()的解釋:
request.getServletPath() 的返回值就是在下面url中定義的值,比如如果按照下面的定義
<url-pattern>
*.do
</url-pattern>
那麼:
http://localhost:8080/servlet/go.do ---------->/go.do
http://localhost:8080/servlet/where/go.do ---------->/where/go.do
這裏有一個小常識,如果
<url-pattern>
/where/*.do
</url-pattern>
那麼地址中只有http://localhost:8080/servlet/where/*.do有效(tomcat 4.0)

在ActionConfig爲保存Moudle Action屬性的一個javabean,它有以下屬性:
* boolean configured 這個對象的所有屬性是否已經被配置完。如果已經完成,那麼在改變其中
任何屬性都會拋出IllegalStateException("Configuration is frozen")
* ModuleConfig moduleConfig 本ActionConfig類所屬於的ModuleConfig。
* String attribute request範圍 或者 session範圍 的屬性名稱,我們將通過這個屬性名稱來
獲取相應的form bean,注意,這個屬性與form bean中定義的名稱是不一樣的。注意以下的方
法:
public String getAttribute() {
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
* String forward 調用RequestDispatcher.forward()方法時所需要的上下文相關的地址。
* String include 調用RequestDispatcher.include()方法時所需要的上下文相關的地址。
* String type Action類的類全名,如果上面的兩個屬性都沒有定義的話,就會用它來處理
request。
* String input
* String multipartClass MultipartRequestHandler實現類的全名
* String name 與本類相關的 form bean 的名稱。
* String parameter 用於struts的擴展,存儲其它的配置信息。struts自己不會用到這個屬性。
* String path 上下文相關的地址,這個地址爲下一個將會進入的地址,這個地址必須要以"/"
開頭。如果使用了extension mapping的話,這個地址將不會帶有擴展名。
* String roles 一個以","分隔的角色名稱。這些角色將會有權限訪問這個request。
* String scope 缺省爲session。
* String prefix,String suffix後綴和前綴。
* boolean unknown 標誌當前的ActionConfig類是否是爲了未知的request path而準備的,
ActionMappings將會根據這個標誌返回這個ActionConfig。
* boolean validate 是否調用validate()方法進行數據校驗。

ActionMapping 繼承自 ActionConfig,它增加了從ModuleConfig查找ActionForword的方法。同
時它還提供了從ModuleConfig查找ExceptionConfig的方法,如果找不到,會通過
type.getSuperclass()方法,根據父類的類名稱繼續查找,直到最後。  
發佈了4 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章