在struts2中在訪問一個菜單鏈接時,我們只需要將相應的package 命名空間和 action的name進行組合,並加上相應的后綴,就可以直接訪問到相應的Action了,那么這個過程是如何進行的,多個相同命名空間的 package是如何滿足互不沖突的呢,這就需要詳細了解struts2中是如何解析路徑信息,並根據訪問路徑尋找相應的action配置了。
整個過程我們可以分成以下幾個步驟進行處理
- 解析xml,將所有可以訪問到的路徑信息進行保存
- 根據訪問請求信息,取其中可用的路徑
- 根據路徑進行查找,最終查找到我們所需要的Action
解析XML
首先我們知道一個package以及action的配置如下所示:
1 2 3 4 5 |
<package name="packageName" extends="struts-default" namespace="/"> <action name="logic" class="detailAction" method="init"> <result name="success">/jsp/success.jsp</result> </action> </package> |
即主要包括包名,命名空間以及action的名稱。那么我們就可以將這一系列的信息通過一個類似PackageConfig的對象進行組織起來。這就是struts2里面所使用的packageConfig。我們來看它的簡單定義:
1 2 3 4 |
protected Map<String, ActionConfig> actionConfigs; ...... protected String name; protected String namespace = ""; |
其中就包括了我們所知道的包名,命名空間,以及使用action名稱和每個action配置的一個map。那么,整個工程中有許多的package,struts如何進行處理呢,那就需要用到這里面的命名空間了。
我們可以把命名空間作為key,然后把每個package里面的所有action配置都作為value進行保存起來,那么使用key表示命名空間,action配置作為value,其中將action的配置再一次map化,使用以下數據結構來進行定義存儲。
1 |
Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>(); |
雙map化,第一個key表示命名空間,第二個key表示action的名稱即路徑,最終的value即每個action的相應信息,這樣針對以下的一個路徑:
1 |
/contextPath/模塊名/功能名/action名/操作.action |
我們只需要將上下文和后面的.action除去,剩下的 /模塊名/功能名/action名/操作,實際上就是一個命名空間和action路徑的一個組合。我們只需要按照/分隔符挨個到map中查找即可。如果存 在 以模塊名為命名空間的package信息,我們就可以直接定義到第二個map,然后 直接使用key 功能名/action名/操作進行直接匹配到相應的action即可以了。
那么整個數據結構的構建過程即在類DefaultConfiguration的方法buildRuntimeConfiguration中,如下所示:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>(); Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>();
//這里即按每個package進行循環,依次放入map中 for (PackageConfig packageConfig : packageContexts.values()) {
String namespace = packageConfig.getNamespace(); Map<String, ActionConfig> configs = namespaceActionConfigs.get(namespace);
Map<String, ActionConfig> actionConfigs = packageConfig.getAllActionConfigs();
for (Object o : actionConfigs.keySet()) { String actionName = (String) o; //這里先取得原先放在大map中的集合信息 ActionConfig baseConfig = actionConfigs.get(actionName); //這里即是將當前package中的action配置再根據命名空間合並到大的map中 configs.put(actionName, buildFullActionConfig(packageConfig, baseConfig)); }
namespaceActionConfigs.put(namespace, configs); } }
return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs); |
解析Action信息
接下來就是查找Action的過程了,當一個請求到來時,struts2首先要得知哪個action能夠處理這個請求,如果沒有可以處理的請求,則進行報 錯或者其它提示。首先,我們知道,這個查找過程必須先去掉上下文之前的所有信息,其次再去掉后綴,只留下中間與命名空間和action名稱組合的字符串, 然后再進行查找。這個過程的描述即在類DefaultActionMapper的方法getMapping中進行的,如下所示:
01 02 03 04 05 06 07 08 09 10 |
ActionMapping mapping = new ActionMapping(); //這一步去頭和上下文 String uri = getUri(request); //這一步去分號和后面的所有信息 int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; //這一步去后續及后面的所有信息,如.action uri = dropExtension(uri, mapping); //這一步為真正的查找過程 parseNameAndNamespace(uri, mapping, configManager); |
查找的具體過程與整個路徑的組織有關,整個實現可以由以下步驟確定。
- 按/進行倒序查找,如果沒有,則認為命名空間為"",整個路徑即是action名稱
- 如果在第1位,即以/開頭,則認為命名空間為/,后面的即是action名稱
- 否則配置了全命名空間搜索,即貪心搜索,即認為所有/之前的都是命名空間,后面即是action名稱
- 否則將所有的在第一步組織的map中進行最長度化匹配,即匹配到的最長的那個命名空間即是我們所要的,后面的則是action名稱
- 最后,如果配置了action路徑中不存在/,則將整個action路徑中/之間的去掉,最后的不包括/的即是action名稱
以上的步驟中,1,2,3,4都是查找過程,最后的第5步為處理過程。整個的實現邏輯即與上述的一致,如下所示:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
String namespace, name; int lastSlash = uri.lastIndexOf("/"); //這里即是第1步,即在沒有上下文時,整個路徑 就沒有/ if (lastSlash == -1) { namespace = ""; name = uri; //第2步,即存在命名空間為/時,或者為""時,這時/即在第1位 } else if (lastSlash == 0) { // ww-1046, assume it is the root namespace, it will fallback to // default // namespace anyway if not found in root namespace. namespace = "/"; name = uri.substring(lastSlash + 1); //第3步,如果貪心搜索,則最長的路徑即為命名空間 } else if (alwaysSelectFullNamespace) { // Simply select the namespace as everything before the last slash namespace = uri.substring(0, lastSlash); name = uri.substring(lastSlash + 1); //第4步,最長化匹配搜索 } else { // Try to find the namespace in those defined, defaulting to "" Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; // Find the longest matching namespace, defaulting to the default for (Object cfg : config.getPackageConfigs().values()) { String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { //這里即判斷是否為最長的,否則的將查找的替換為更長的 if (ns.length() > namespace.length()) { namespace = ns; } } } name = uri.substring(namespace.length() + 1); }
//這是第5步,針對action名稱處理 if (!allowSlashesInActionNames && name != null) { int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } |
這樣即完成了查找過程,但這里僅是一個查找,並不是一個最終匹配,因為還存在着根據這個查找到的匹配結果如何映射到最終action對象的過程,即映射到actionConfig上。
映射actionConfig
整個映射過程可以分成以下幾個步驟進行
- 首先,根據默認的匹配進行查找,如果能夠查找到,就認為是最終的actionConfig,在其中也會對action名稱進行模式匹配
- 否則對命名空間進行*號匹配,嘗試進行模式匹配
- 最后則嘗試使用默認的空命名空間進行查找
1 其實就是首先根據命名空間查找相應的子map,即actionName和actionConfig的集合,然后再在子map中查找,如果子map中查不到,則使用模式匹配。再找不到就進入到步驟2了。
2 在步驟2時,首先也會使用模式匹配查找到對應的命名空間,如果有可匹配的命名空間,就使用該命名空間再次重復第一步操作
3 還沒找到,就使用默認的命名空間即""進行查找
整個實現在類DefaultActionMapper的getActionConfig方法中,如下所示:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//使用第一步操作進行匹配 ActionConfig config = findActionConfigInNamespace(namespace, name);
//使用第二步操作進行命名空間的模式匹配 // try wildcarded namespaces if (config == null) { NamespaceMatch match = namespaceMatcher.match(namespace); if (match != null) { config = findActionConfigInNamespace(match.getPattern(), name);
// If config found, place all the matches found in the namespace processing in the action's parameters if (config != null) { config = new ActionConfig.Builder(config) .addParams(match.getVariables()) .build(); } } }
//使用第3步的默認匹配 // fail over to empty namespace if ((config == null) && (namespace != null) && (!"".equals(namespace.trim()))) { config = findActionConfigInNamespace("", name); }
return config; |
至此,整個定位過程結束。如果此處仍找不到actionConfig對象,那么就直接拋出相應的異常了,即常見的
There is no Action mapped for namespace {0} and action name {1}.
There is no Action mapped for action name {0}.
轉載請標明出處:i flym
本文地址:http://www.iflym.com/index.php/code/201302270001.html