struts2中如何根据请求路径定位到详细的访问action

在struts2中在访问一个菜单链接时,我们只需要将相应的package 命名空间和 action的name进行组合,并加上相应的后缀,就可以直接访问到相应的Action了,那么这个过程是如何进行的,多个相同命名空间的package是如何满足互不冲突的呢,这就需要详细了解struts2中是如何解析路径信息,并根据访问路径寻找相应的action配置了。

整个过程我们可以分成以下几个步骤进行处理

  1. 解析xml,将所有可以访问到的路径信息进行保存
  2. 根据访问请求信息,取其中可用的路径
  3. 根据路径进行查找,最终查找到我们所需要的Action

解析XML

首先我们知道一个package以及action的配置如下所示:

	<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。我们来看它的简单定义:

    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化,使用以下数据结构来进行定义存储。

Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>();

双map化,第一个key表示命名空间,第二个key表示action的名称即路径,最终的value即每个action的相应信息,这样针对以下的一个路径:

/contextPath/模块名/功能名/action名/操作.action

我们只需要将上下文和后面的.action除去,剩下的 /模块名/功能名/action名/操作,实际上就是一个命名空间和action路径的一个组合。我们只需要按照/分隔符挨个到map中查找即可。如果存在 以模块名为命名空间的package信息,我们就可以直接定义到第二个map,然后 直接使用key 功能名/action名/操作进行直接匹配到相应的action即可以了。

那么整个数据结构的构建过程即在类DefaultConfiguration的方法buildRuntimeConfiguration中,如下所示:

        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中进行的,如下所示:

        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);

查找的具体过程与整个路径的组织有关,整个实现可以由以下步骤确定。

  1. 按/进行倒序查找,如果没有,则认为命名空间为"",整个路径即是action名称
  2. 如果在第1位,即以/开头,则认为命名空间为/,后面的即是action名称
  3. 否则配置了全命名空间搜索,即贪心搜索,即认为所有/之前的都是命名空间,后面即是action名称
  4. 否则将所有的在第一步组织的map中进行最长度化匹配,即匹配到的最长的那个命名空间即是我们所要的,后面的则是action名称
  5. 最后,如果配置了action路径中不存在/,则将整个action路径中/之间的去掉,最后的不包括/的即是action名称

以上的步骤中,1,2,3,4都是查找过程,最后的第5步为处理过程。整个的实现逻辑即与上述的一致,如下所示:

   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

整个映射过程可以分成以下几个步骤进行

  1. 首先,根据默认的匹配进行查找,如果能够查找到,就认为是最终的actionConfig,在其中也会对action名称进行模式匹配
  2. 否则对命名空间进行*号匹配,尝试进行模式匹配
  3. 最后则尝试使用默认的空命名空间进行查找

1    其实就是首先根据命名空间查找相应的子map,即actionName和actionConfig的集合,然后再在子map中查找,如果子map中查不到,则使用模式匹配。再找不到就进入到步骤2了。
2    在步骤2时,首先也会使用模式匹配查找到对应的命名空间,如果有可匹配的命名空间,就使用该命名空间再次重复第一步操作
3    还没找到,就使用默认的命名空间即""进行查找

整个实现在类DefaultActionMapper的getActionConfig方法中,如下所示:

//使用第一步操作进行匹配
            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
本文地址:https://www.iflym.com/index.php/code/201302270001.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注