struts2中如何根據請求路徑定位到詳細的訪問action


在struts2中在訪問一個菜單鏈接時,我們只需要將相應的package 命名空間和 action的name進行組合,並加上相應的后綴,就可以直接訪問到相應的Action了,那么這個過程是如何進行的,多個相同命名空間的 package是如何滿足互不沖突的呢,這就需要詳細了解struts2中是如何解析路徑信息,並根據訪問路徑尋找相應的action配置了。

整個過程我們可以分成以下幾個步驟進行處理

  1. 解析xml,將所有可以訪問到的路徑信息進行保存
  2. 根據訪問請求信息,取其中可用的路徑
  3. 根據路徑進行查找,最終查找到我們所需要的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);

查找的具體過程與整個路徑的組織有關,整個實現可以由以下步驟確定。

  1. 按/進行倒序查找,如果沒有,則認為命名空間為"",整個路徑即是action名稱
  2. 如果在第1位,即以/開頭,則認為命名空間為/,后面的即是action名稱
  3. 否則配置了全命名空間搜索,即貪心搜索,即認為所有/之前的都是命名空間,后面即是action名稱
  4. 否則將所有的在第一步組織的map中進行最長度化匹配,即匹配到的最長的那個命名空間即是我們所要的,后面的則是action名稱
  5. 最后,如果配置了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

整個映射過程可以分成以下幾個步驟進行

  1. 首先,根據默認的匹配進行查找,如果能夠查找到,就認為是最終的actionConfig,在其中也會對action名稱進行模式匹配
  2. 否則對命名空間進行*號匹配,嘗試進行模式匹配
  3. 最后則嘗試使用默認的空命名空間進行查找

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM