在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