官术网_书友最值得收藏!

第3章 提綱挈領—-Struts2概覽

3.1 Struts2的來世今生

作為Apache旗下非常重要的開源項目,Struts這個項目的歷史發展進程可以說比較特殊。Struts2的來世今生,從一個側面反映出了整個Web開發的發展歷程。在這里,我們簡單對Struts項目的發展歷程做一個簡單的介紹,從而幫助大家對Web層框架的發展有更加深刻的了解。

查閱一下Struts的歷史就可以發現,最早的Struts可以追溯到2000年3月31日,當時Apache有一則新聞,宣布開始編寫一個新的Web開發框架—Struts。

Craig McClanahan has contributed a small framework for building web applications using the Model-View-Controller (MVC) design pattern commonly known as "Model 2" to the Jakarta project. The "Struts" project will serve as a platform for exploring the optimum approaches to implementing this design pattern, in a manner that can facilitate tool-based generation of application components.

項目取名為“Struts”,有“基礎構建”的含義,在那個開發框架尚處于朦朧階段的年代,“基礎構建”無疑是每個程序員夢寐以求的東西。在新聞發布后不久,Apache便發布了Struts1.0.X的若干個版本和針對Struts1.0.X的升級版本Struts1.1.X。自此以后,Struts1.X系列開發框架就開始在世界范圍內流行起來。在很長一段時間里,其流行程度幾乎可以說壟斷了整個Web開發領域,成為Web開發領域的實際開發標準。

Struts1.X系列開發框架能夠如此流行的主要原因在于:首先,它是Apache出品,所謂樹大好乘涼,Apache巨大的社區開發優勢對它在全世界的流行起到了不可估量的作用;其次,Struts1.X也是較早實現MVC模式中“Model 2”概念的Web開發框架,在一定程度上它占有“天時”方面的優勢;最后,各大中間件廠商尤其是IBM等公司對Struts1.X的巨大支持,為Struts1.X的推廣起到了推波助瀾的作用。

隨著時代的進步,越來越多的程序員在使用Struts1.X進行開發的過程中發現Struts1.X在設計上存在著嚴重不足。與此同時,各種各樣的Web層開發框架也如雨后春筍般涌現出來。在這些框架中,有一個來自于Opensymphony開源社區的優秀框架Webwork2逐漸被大家熟識和理解,并不斷發展壯大。2004年12月14日,Webwork2.1.7版本發布,成為Webwork2的一個重要里程碑,它以優秀的設計思想和靈活的實現,吸引了大批Web層開發人員投入它的懷抱。在這之后,Webwork2.2.X的若干個版本依次發布,并開始支持JDK1.5的相關特性,極大地解放了Web層開發的生產力。

或許是看到了Struts1.X發展上的局限性(主要是Struts1.X在設計思想和技術實現上的種種局限性),Apache社區與Opensymphony開源組織在2005年12月14日宣布未來的Struts項目將與Webwork2項目合并,并聯合推出Struts2,通過Apache社區的人氣優勢與OpenSymphony的技術優勢,共同打造下一代的Web層開發框架。當時的新聞寫到:

Apache Struts, the leading web application framework for Java, and Open Symphony WebWork, a leader in technical innovation, are working to merge their communities and codebases.

從這次合并中,我們可以非常明顯地看到Apache社區在技術層面上的一個重大妥協。雖然Struts1.X借助Apache的社區力量占領了市場和人氣,然而在技術上,Webwork2卻完勝Struts1.X,合并之后的Struts2將完全采用Webwork2作為其代碼的基礎,摒棄Struts1.X的所有設計和代碼。

所以,研究Struts2時必須明確一個非常重要的結論:

結論 Struts2來自于Webwork2,并且與Struts1.X完全不兼容。

認識這一點對所有Struts開發人員來說非常重要。而本書所講解的內容,也全部都是針對Struts2而不是Struts1.X,這將成為我們之后所有討論的基礎。

經過幾年的發展,Struts2又陸續發布了Struts2.0.X、Struts2.1.X和Struts2.2.X的若干個版本,伴隨著版本的不斷升級,我們不僅從中獲得了越來越便利的開發模式,也可以體會到整個Web開發的發展歷程,從一個側面印證了技術的發展與設計思想的不斷進步。

3.2 Struts2面面觀

在深入研究Struts2內部的實現機理之前,我們將從Struts2的運行環境、應用場景以及核心技術這三個方面對Struts2進行簡單的介紹。這三個不同的方面是描述Struts2外部環境的三個不同角度,能夠幫助我們站在一個更高的高度對Struts2作為表示層框架在Web開發中所起的作用和Struts2與其他核心技術之間的關系有一個更深刻的理解。

3.2.1 Struts2的運行環境

在上一章中,我們就談到本書的核心內容是通過對Struts2的分析,幫助讀者領略Web開發的方方面面。從這一點上講,我們的話題也就始終無法脫離Web開發以及Web應用程序的運行環境。Struts2在典型的三層結構的開發模式中被視作“表示層”的解決方案。因而,其最為核心的內容就是和Web容器打交道,幫助我們處理Http請求。

結論 Struts2是一個運行于Web容器的表示層框架,其核心作用是幫助我們處理Http請求。

請讀者牢記這一條結論,因為這條結論不僅是整個Struts2框架的指導思想,也規定了Struts2作為一個框架所涉及的問題作用域。從這個結論中,我們很容易得到與Struts2的運行環境相關的兩條推論:

推論 Struts2的運行環境是Web容器。

這條推論顯而易見,其中卻也隱含著許多重要內容。而最為重要的就是Struts2對其運行環境的Web容器有版本要求。我們知道,運行于Web容器中的程序,首先必須遵循基本的開發標準和規范:Servlet標準和JSP標準等等。而Java中的Web開發標準有著不同的版本,不同的Web服務器對于Servlet標準和JSP標準的支持程度也是不同的。對于Struts2而言,它所支持的Servlet標準的最低版本要求是2.4,相應的JSP標準的最低版本要求是2.0。這就對使用Struts2作為開發框架的應用程序的運行環境提出了要求,這一要求就像Struts2要求必須運行在JDK1.5版本之上一樣。

結論 Struts2通過擴展實現Servlet標準來處理Http請求。

如果把整個Web容器看作是一個黑盒,Struts2無非也只是黑盒中所運行的一段程序代碼段,如圖3-1所示。

圖3-1 Web容器的黑盒模型

從圖中我們可以看到,Struts2這個框架在其中的作用實際上只是處理Http請求(Request),并進行內部處理(處理的過程被黑盒屏蔽了,我們暫時也無須關心),再進行Http返回(Response)。而整個這個過程的代碼級別的實現,無論如何進行封裝,都離不開對Servlet標準或者JSP標準所指定的底層API的調用。從這個角度來說,Struts2只是實現了一個具備通用性的Http請求處理機制,并且能夠被我們的應用程序使用和擴展而已。

我們在這里花費一些筆墨向讀者強調Struts2運行環境的目的在于提醒讀者不應該忘記程序運行的基礎條件。不要把任何一個框架看得很神秘,它們的本質也只不過是實現了基本的開發協議或者開發規范的程序集合而已。

3.2.2 Struts2的應用場景

在了解了Struts2的運行環境之后,Struts2的應用場景的問題也就能夠迎刃而解了。當我們使用Java語言編寫一個Web應用程序時,Struts2或許就能夠幫得上忙。這就是Struts2的應用場景:幫助我們編寫Web應用程序。

細心的讀者會發現我們在這里使用了“或許”兩個字。為什么要使用“或許”呢?Struts2是否能夠成為所有Web應用的最佳選擇呢?這個問題,我們還得回到上一章中曾經提到過的一個Struts2的官方表述,如圖3-2所示。

圖3-2 Struts2官方對Struts2應用場景的解釋

非常明顯,Struts2官方為我們描述的Struts2的應用場景是:

結論 Struts2并不適合所有的Web應用,但它卻是復雜的、可擴展的Web應用的一劑良藥。

因為當需要建立一個由復雜的業務邏輯和眾多頁面構成的Web應用時,我們不得不采用分層開發模式。例如,我們可以采用典型的三層開發模式,將表示層、業務邏輯層和持久層的邏輯完全分開,從而獲得更好的程序可擴展性和可維護性。此時,Struts2就可以作為一個表示層的解決方案,幫助我們進行與表示層相關的開發工作,并提供在表示層范圍之內高度的可擴展性和可維護性。

由此可見,在討論Struts2的應用場景時,必須首先明確我們要開發的Web應用的規模和實際情況,并且理解Struts2自身的作用范圍,從而能夠合理地選擇最適合的開發框架。

3.2.3 Struts2的核心技術

作為一個Web開發的解決方案,Struts2并不是一個可以獨立運行的開源框架。Struts2的實現,首先將基于最為基本的Web開發標準。除此之外,Struts2自身還依賴于一些其他的開源框架和解決方案。我們在這里簡單歸納一下這些Struts2的實現所涉及的核心技術,在本書的核心技術篇中,我們將對其中的核心技術進行詳細分析。

3.2.3.1 Struts2與表示層技術

Struts2首先運行于Web容器之中。因而,它的核心依賴就是Web容器對于Servlet標準和JSP標準的實現。我們在之后的章節中對Struts2的主要分析,也將圍繞著Struts2如何擴展實現這些基本的標準實現來展開。

作為一個服務于表示層的解決方案,Struts2有時候不得不與許多其他的表示層技術進行整合。例如,以Freemarker或者Velocity為核心的模板技術、構建Flash應用的Flex技術、Ajax技術等等。這些技術往往本身自成體系,而Struts2需要做的只是通過擴展實現一些Servlet標準與這些技術進行底層溝通從而完成與這些技術的整合。

Struts2與這些表示層技術的整合,往往通過“插件”的方式進行。有關插件(Plugin)的相關知識,我們將在本書的第12章中重點展開。在這里,我們所需要了解的是:開發人員可以根據項目實際情況,選擇合適的插件并將其引入到Struts2的運行環境中。Struts2在運行時能夠自動識別這些插件,從而完成與相關技術的自動整合。

3.2.3.2 Struts2與設計模式

設計模式(Design Pattern)是我們在日常編程中經常聽到的一個名詞。實際上所有的設計模式只不過是代碼級別最佳實踐的具體表現。

因此,任何設計模式都不是什么核心技術,也不是Struts2的實現嚴格依賴的內容主體。然而,Struts2的內部實現卻離不開這些設計模式。有一些核心的設計模式,甚至貫穿了整個Struts2的邏輯主線,成為Struts2內部實現中不可或缺的重要組成部分。

在Struts2中,我們將先后接觸到命令(Command)模式、ThreadLocal模式、裝飾(Decorator)模式、策略(Strategy)模式、構造(Builder)模式、責任鏈(Chain Of Responsibility)模式、代理(Proxy)模式等等。這些設計模式的反復使用,使得Struts2的實現本身就充滿了最佳實踐。我們將在本書的第4章中重點就Struts2所用到的一些核心設計模式進行深入剖析,幫助讀者更加深刻地理解這些設計模式在框架中的運用場景。

3.2.3.3 Struts2與OGNL

有過Web程序開發經驗的讀者會發現,表達式引擎是Web編程必不可少的重要元素。所謂表達式引擎,指的是通過程序建立起某個實體對象與某種公式表達之間的聯系。在Java世界,這種聯系具體表現為:使用某些符合特定規則的字符串表達式來對Java中的對象進行讀和寫的操作。

表達式引擎對Web開發的重要之處在于它在一定程度上解決了Web應用與Java世界之間的溝通問題。既然要使用Java來開發Web應用,就必須使Java的編程要素能夠與Web瀏覽器之間在數據層面保持良好的溝通,而這種溝通就是通過表達式引擎來完成的。

Struts2作為Web層的開發框架,也勢必要借助一個強大的表達式引擎來實現上述功能。因而,Struts2選擇了在表達式引擎這一領域表現極為非常出色的OGNL作為其所依賴的表達式引擎。換言之,OGNL是Struts2運行所依賴的基本核心技術之一。OGNL的意義不僅在于完成不同形式數據之間的轉化和通信,它也是Struts2實現視圖層的基本依據。在之后的章節中,我們會發現這將成為Struts2區別于其他Web層框架的實現視圖層與Web服務器之間數據交互的最為顯著的特點。

OGNL表達式引擎也有較長的歷史,它的早期版本的性能曾經遭到質疑。不過,它的性能問題已經在最新的3.0版本中得到了極大的改善。Struts2的最新版本Struts2.2.1所依賴的OGNL為3.0。有關OGNL的相關知識,我們將在第5章詳細介紹。

3.2.3.4 Struts2與XWork

了解了Struts2的歷史之后,我們知道Struts2來源于Webwork2。Webwork2之所以能夠在技術上擊敗Struts1.X而成為新的框架Struts2的構建基礎,主要是源于其優秀的設計思想。或許我們很難用一句話來準確描述這一設計思想的優越性體現在何處,但是當我們試圖總結這種優秀思想中所蘊含的核心基石時,XWork將當仁不讓地成為其中關鍵字的第一位。我們可以在XWork的官方網站上找到對XWork的一個大概描述:

XWork is a command-pattern framework that is used to power Struts 2 as well as other applications. XWork provides an Inversion of Control container, a powerful expression language, data type conversion, validation, and pluggable configuration.

XWork是Opensymphony開源組織貢獻的另外一個開源項目,從其官方網站的介紹來看,XWork不僅提供了一系列基礎構件,其中包括:一個IoC的容器、強大的表達式語言(OGNL)支持、數據類型轉化、數據校驗框架、可插拔的功能模塊(插件模式)及其配置,并且在這一系列的基礎構件之上,實現了一套基于Command設計模式的“事件請求執行框架”。

什么是“事件請求執行框架”呢?首先,這是一個基于“請求-響應”的處理器,能夠對某一類“請求”做出相應的邏輯處理,并返回“響應”結果。其次,它定義了一套完整的事件邏輯處理的步驟,并且為每個步驟都提供了足夠的擴展接口,使得整個事件的執行體系更為豐富、更加具備層次感和可擴展性。因而,“事件請求執行框架”就如同一條定義好的生產流水線,能夠為我們提供完整的事件處理模型。回顧一下我們在上一章中提到的一個事件處理流水線的問題,XWork正是這樣一個解決方案。

我們知道,所有B/S程序都是典型的基于“請求-響應”模式的Web應用。因而XWork天然地成了處理這種應用最合適的方案。有了XWork作為Struts2所依賴的底層核心,使得Struts2只需要關注與Web容器打交道的部分,而把其余的工作交給XWork即可。當Struts2收到一個Http請求時,Struts2只需要接收請求參數,交給XWork完成執行序列,當XWork執行完畢后,將結果交還Struts2返回相應的視圖。可以看到,在整個過程中,XWork是這個“請求-響應”模式的執行核心。

XWork對于Struts2的地位如此重要,以至于Struts2的重大版本總是跟隨著XWork的升級而變更。需要注意的是,Struts2的不同版本對XWork的版本依賴也是不同的,它們甚至是不兼容的。下面列出了Struts2發布以來它與XWork版本的兼容性情況:

struts2-core-2.0.x—xwork2.0.x

struts2-core-2.1.x—xwork2.1.x

struts2-core-2.2.x—xwork2.2.x

XWork不僅是Struts2的核心實現,也可以用于一切基于Command模式的Java程序。在實現Command模式時,XWork在其周圍定義了豐富的執行層次,在每個執行層次中,都有足夠的擴展點,使得我們可以將XWork視作一個工具包,簡化我們的開發。

3.3 多視角透析Struts2

Struts2的外部環境并不復雜,因為其核心內容非常明確:探究Struts2運行時所必需的基本要素。我們對Struts2的運行環境和Struts2所依賴的核心技術的講解,主要是為了讓讀者了解Struts2能夠順利運行的條件。

在明確Struts2的外部環境之后,我們討論的話題就將轉向Struts2本身。在本節中,我們將從宏觀和微觀這兩個不同的視角,闡述Struts2的總體架構和內部元素構成,以此揭開Struts2的神秘面紗。

3.3.1 透視鏡—-Struts2的宏觀視圖

Struts2的宏觀視圖是指站在整個框架的角度,了解程序的運行可以劃分為哪些邏輯運行主線。對于一個框架的邏輯運行主線的研究,也是我們分析一個框架最為重要的切入點。而這一切入點,就位于Struts2的核心入口程序之中。

3.3.1.1 Struts2的核心入口程序

Struts2的核心入口程序,從功能上講必須能夠處理Http請求,這是表示層框架的基本要求。為了達到這一要求,Struts2毫無例外地遵循了Servlet標準,通過實現標準的Filter接口來進行Http請求的處理。我們通過在web.xml中指定這個實現類,就可以將Struts2框架引入到應用中來,如代碼清單3-1所示。

代碼清單3-1 web.xml

          <filter>
              <filter-name>struts</filter-name>
            <filter-class>org.apache.struts2.dispatcher.ng.filter.
          StrutsPrepareAndExecuteFilter</filter-class>
          </filter>
          <filter-mapping>
              <filter-name>struts</filter-name>
              <url-pattern>/*</url-pattern>
          </filter-mapping>

打開StrutsPrepareAndExecuteFilter的源碼,可以發現它只是一個實現了Filter接口的實現類。其方法列表如圖3-3所示。

圖3-3 StrutsPrepareAndExecuteFilter方法列表

根據Servlet標準中Filter的生命周期的相關知識,我們知道Filter中所定義的方法具有完全不同的執行時間段和生命周期,它們的執行互不影響,沒有交叉。而Filter的生命周期也成為我們對整個Struts2進行運行邏輯主線劃分的主要依據。

第一條主線 — Struts2的初始化: init方法驅動執行

第二條主線 — Struts2處理Http請求:doFilter方法驅動執行

在這里,我們首先不對StrutsPrepareAndExecuteFilter的源碼做深入的探討,不過可以先把這個入口程序的基本結構和功能結合我們對Struts2劃分的兩條邏輯主線,用示意圖的形式表達出來,從而幫助讀者對入口程序的運行邏輯主線有個初步的認識,如圖3-4所示。

圖3-4 Struts2入口程序執行示意圖

從圖中,我們可以清晰地看到Struts2的兩條邏輯主線之間由一條分隔符分開。不同的邏輯主線之間完全沒有交叉,驅動它們執行的時間節點和觸發條件都不同。因而,我們日后對Struts2運行邏輯的分析,也將圍繞著這兩條主線分別展開。

Struts2入口程序的示意圖,可以看作是對Web容器黑盒模型的第一層細化,讀者在這里應該更多關心程序運行的基本方向,其內部細節有待我們在之后的章節為大家一一解開。

3.3.1.2 Struts2的初始化主線

Struts2的初始化主線發生在Web應用程序啟動之初,由入口程序的init方法驅動執行完成。這條運行主線的主要特點有:

僅在Web應用啟動時執行一次

由于這條主線由Filter中的init方法驅動執行,執行完畢后,該主線結束。也就是說,這條主線本身不參與后面任何Http請求的處理過程,無論Struts2之后面再收到多少Http請求,這條主線都不會重復執行。

init方法的執行失敗將導致整個Web應用啟動失敗

如果在init方法執行的過程中發生異常,整個Web應用將無法啟動。這個特點從框架規范的角度規定了我們必須確保初始化過程的順利執行。因為在這個過程中,所有框架內部定義的元素將被初始化,并支撐起整個Struts2進行Http處理的過程。

這兩大特點本身其實來源于Filter這個Servlet規范的基本運行特性。然而,這兩大特點卻也為應用程序在框架的基礎之上進行邏輯擴展提供了理論上的指導。在之后有關如何擴展框架的話題討論中,我們可以看到所有的擴展方案都將基于這兩大特點進行設計。

那么,Struts2的初始化主線到底做了什么呢?對應于Struts2初始化的運行特點,Struts2的初始化主線也有兩大主要內容:

框架元素的初始化工作

這一初始化工作包含了對框架內部的許多內置對象的創建和緩存。我們發現,對于框架初始化工作的基本要求,就是在整個框架的運行過程中僅執行一次,這正好符合這條主線的基本特點。

控制框架運行的必要條件

框架的可擴展特性保證了我們可以在應用層面對框架的運行參數和執行模式進行定制化,而框架則有必要對這種定制化進行正確性校驗。當這種校驗失敗時,Web應用的啟動會失敗。這也就是Struts2在框架級別所能夠提供的運行期的檢查。

初始化主線貫穿了Struts2對其內置對象的創建和緩存的過程,這一過程相當于把整個Struts2作為一個框架的運行環境完整地創建出來。這條主線的順利運行,為之后的Http請求處理主線提供了必要的框架運行環境。

我們在這里所說的運行環境和Struts2自身的運行環境不同,它是指建立在Web服務器之上,框架自身運行所必需的內置對象的集合。為了更好地對這些內置對象進行管理,Struts2引入了框架級別“容器”的概念。因而Struts2的初始化主線,實際上最終轉化為對這個“容器”的初始化過程。有關這個“容器”的定義和初始化過程的細節,我們將在第5章為讀者解開謎團。

3.3.1.3 Struts2的Http請求處理主線

Struts2的Http請求處理主線是Struts2的核心主線,包含了Struts2處理Http請求、進行必要的數據處理和處理數據返回的全部過程。這條主線將在任何滿足web.xml中所指定的URL Pattern的Http請求發生時進行響應,由doFilter方法負責驅動執行。

回顧一下Struts2核心入口程序的流程圖(圖3-4),我們可以看到Struts2的Http請求處理主線又被一條分割線劃分成了兩個不同的執行階段:

第一階段—Http請求預處理

在這個階段中,程序執行的控制權在Struts2手上。這個階段的主要工作是針對每個Http請求進行預處理,為真正的業務邏輯執行做必要的數據環境和運行環境的準備。

程序代碼在這個階段有一個非常顯著的特點:依賴于Web容器,并時時刻刻將與Web容器打交道作為主要工作。

第二階段—XWork執行業務邏輯

在這個階段,程序執行的控制權被移交給了XWork。Struts2在完成Http請求的預處理之后,將Http請求中的數據封裝成普通的Java對象,并由XWork負責執行具體的業務邏輯。

程序代碼在這個階段的特點和第一階段完全相反:不依賴于Web容器,完全由XWork框架驅動整個執行的過程。

從Struts2對于Http請求的處理過程中,我們可以看出Struts2的核心設計理念在于解耦。所謂解耦,實際上是盡可能地消除核心程序對外部運行環境的依賴,從而保證核心程序能夠更加專注于應用程序的邏輯本身。在Struts2中,我們所說的外部運行環境就是Web容器。我們在這里可以看到,Struts2的核心設計理念與Struts2的運行環境居然是一個矛盾體!

結論 Struts2的核心設計理念在于消除核心程序對運行環境(Web容器)的依賴,而這一過程也是Struts2的解耦過程。

這種設計實現與目的之間的矛盾,卻是Struts2的設計始終圍繞著Web開發中的最佳實踐的最有力證明,也是Struts2從設計理念開始,就優于其他表示層框架的精要所在。在解耦方面,Struts2也確實做到了2個不同的層面,從而使整個設計更加突顯出其優秀之處:

從代碼上進行物理解耦

Struts2將第一階段中的代碼整合到struts2-core-2.2.1.jar,而將第二階段中的代碼整合到xwork-core-2.2.1.jar。

將邏輯分配到不同的執行階段

Struts2將處理數據的邏輯和處理業務的邏輯分配到2個不同的執行階段,使得我們對于代碼邏輯的關注點更為清晰。

因此,正如我們在之前的章節所談到的,嚴格意義上的Struts2,實際上由2個不同的框架所組成。一個是真正意義上的Struts2,另外一個是XWork。從職責上來說,XWork才是真正實現MVC的框架,Struts2的工作是在對Http請求進行一定處理后,委托XWork完成真正的邏輯處理。將Web容器與MVC實現分離,是Struts2區別于其他Web框架的最重要的特性,也是最值得我們品味的一個宏觀設計思路。當讀者真正理解了其中的奧秘,相信也就真正掌握Web開發之道了。

3.3.2 顯微鏡 — Struts2的微觀元素

在了解了Struts2的宏觀面之后,我們再來一起探究一下構成這些宏觀面的微觀元素。同樣,不同的主線和不同的階段所承擔的職責不同,構成它們的微觀元素也不盡相同。在本節中,我們將列出每條主線和每個執行階段的主要組成元素。或許在這其中大家會接觸到許多新名詞,讀者不妨首先感性地認識一下這些概念性的名詞,可以不求甚解,因為我們會在后續的章節中對Struts2的這些元素一一展開分析和講解。

3.3.2.1 第一條主線 — Struts2的初始化

在對Struts2初始化主線的宏觀分析中,我們曾經談到為了幫助更好地管理Struts2中的內置對象,Struts2引入了一個“容器”的概念,將所有需要被管理的對象全部置于容器之中。因而,整個Struts2初始化過程也始終圍繞著這個“容器”展開。除了“容器”,Struts2中的另一類配置元素PackageConfig,也是Struts2初始化的主要內容之一。如果我們從“數據 + 行為”的角度來分析,那么構成Struts2整個初始化過程的主要元素,就可以分為數據結構的定義和初始化行為的操作接口兩個部分。

從數據結構定義的角度,“容器”順理成章地成為Struts2初始化主線中的核心構成元素,而PackageConfig作為事件請求映射的配置元素也成為我們所需要重點關注的構成元素。它們的接口定義和實現類如表3-1所示。

表3-1 Struts2中的容器及其實現類

表3-1的定義從數據結構的角度指出了Struts2初始化流程的主要對象是哪些。而整個初始化的操作過程,則由另外兩個相輔相成的元素配合共同完成,它們分別是加載接口(Provider)構造器(Builder),其相關元素如表3-2所示。

表3-2 Struts2中容器的加載接口(Provider)和容器的構造器(Builder)

Struts2初始化主線中還有一些輔助元素,它們主要用于承載這些配置加載接口并在初始化時驅動整個初始化流程的順利執行。相關元素如表3-3所示。

表3-3 Struts2初始化主線中的輔助元素

Struts2的初始化是一個非常復雜的流程。在這里我們僅僅給出了部分主要的數據結構和基礎操作接口。讀者可以使用IDE的源碼查看功能找到這些接口的眾多實現類。從這些實現類中,大家會發現這些實現類實際上是對整個Struts2配置元素的一個總串聯,之后的章節我們會通過源碼分析它們的聯系和運行機理。

3.3.2.2 第二條主線—第一階段:Http請求預處理

在這個階段,我們知道程序的執行控制權還在Struts2手中。所以在這個階段中所涉及的主要微觀元素,都是Struts2的類。這些類的主要職責是與Web容器打交道。因而,從設計原則上,為了保持解耦,這個階段做了大量的對象創建和對象轉化的工作。當這些工作完成之后,就能交付第二個階段繼續執行。這個階段的主要微觀元素如表3-4所示。

表3-4 Struts2進行Http請求預處理階段的主要微觀元素

雖然這個階段所涉及的元素很少,但是其中的Dispatcher卻是整個Struts2框架的核心。Dispatcher被稱為核心分發器,是Struts2進行Http請求處理的實際場所。在整個處理流程中,Dispatcher不僅是Http請求預處理的實際執行者,更是將Http請求與Web容器進行解耦并進行邏輯處理轉發的執行驅動核心。事實上,我們對Struts2框架內部機理的研究,都將以Dispatcher作為重要的切入點而展開。

3.3.2.3 第二條主線 — 第二階段:XWork執行業務邏輯

在這個階段,程序的執行控制權被移交到了XWork框架中。我們可以想象位于這個執行階段的微觀元素區別于第一個階段的顯著特點:它們都是XWork框架所定義的類。其相關微觀元素的定義如表3-5所示。

表3-5 XWork執行業務邏輯階段的主要微觀元素

在這里,我們形象地把XWork比喻成一條生產流水線。在這其中的每個元素,就像是生產線中的重要組成部分。XWork框架就像一個完整的事件執行器,進入框架中的事件就如同進入生產線中的原材料,會按照生產線中的定義依次執行并產生結果。

表3-5中的七個元素,被稱為XWork的七大元素,貫穿了XWork事件執行器的整個生命周期。它們各司其職、精心配合,提供了事件執行框架足夠的擴展接口。不僅如此,它們之間的調用關系也成為XWork框架設計中的經典。這些元素的調用關系,如圖3-5所示。

圖3-5 XWork元素的調用關系圖

這幅圖不僅包含了XWork框架中各個元素的調用關系,還把整個XWork框架置于Web容器這樣一個大的環境之中,同時給出了XWork框架的調用核心Dispatcher。我們在這里不再對這幅圖做深入的剖析,讀者可以大致了解XWork中這些元素的結構和層次關系。在第8章中,我們將從數據流和控制流這兩個不同的角度,對這張圖的程序流轉方向進行詳細的分析。

3.4 Struts2的配置元素

上一節中,我們分別從宏觀層面和微觀層面對Struts2進行了初步的透視分析。從框架中提煉出了Struts2運行的2條主線和2個執行階段,并以此為基礎列出了每條主線和每個執行階段的核心元素。2條主線和2個執行階段的概念之所以被反復提及,是因為Struts2的所有內容全都無法脫離這些概念而獨立存在,這些概念不僅支撐起了Struts2的核心構架,同時也是我們研究Struts2中構成元素和運行機理的主要依據。

我們在這里所說的“構成元素”“運行機理”,實際上從另外一個角度表述了程序的構成方式。任何程序,如果我們從組織結構上進行分析,總是由兩大類元素組成:一類用于描述問題,這類元素我們通常稱之為數據結構(構成元素);另一類元素則是在數據結構的基礎之上執行的邏輯代碼,這類元素我們通常稱之為算法(運行機理)。數據結構和算法的有機結合,構成了可運行的程序主體。這其實也是我們經常聽到的一條結論:

結論 程序 = 數據結構 + 算法 (構成元素 + 運行機理)

對“構成元素”的研究,有助于我們站在全局的觀點來審視支撐整個Struts2運行的底層核心。而這些構成元素,需要一個貫穿始終的粘合劑,不僅能夠以一定的形式表現出這些構成元素互相之間的邏輯關系,同時能夠將它們的執行邏輯串聯起來。這種粘合劑,實際上就是框架的核心配置。在本節中,我們就來重點討論一下Struts2的核心配置的表現形式、元素構成和運行機理。

3.4.1 Struts2配置詳解

配置就像是程序的影子,與程序總是如影隨形。無論是什么框架,配置總是作為一個重要的組成部分,在框架的運行過程中發揮作用。同時,配置所起的不同作用在一定程度上也決定了配置的存在形式。

3.4.1.1 配置概覽

Struts2提供了多種可選的配置文件形式。根據這些配置文件的名稱、所在位置、作用范圍和用途我們制作了一張圖表,如表3-6所示。

表3-6 Struts2配置文件的表現形式

表3-6就是Struts2中配置文件的一個概覽。可以看到在表頭部分,我們分別使用了配置文件、所在位置、作用范圍、用途來對配置文件進行大致的描述。接下來,我們分別從不同的角度來深入挖掘一下隱藏在配置文件背后的秘密。

3.4.1.2 配置的表現形式

從表3-6的第一列中,我們可以看到Struts2所支持的所有配置形式:其中既有XML文件形式,也有Properties文件形式。這些不同形式的配置文件,它們格式不同、所在位置不同、作用范圍也不同。那么這些配置文件有沒有一個主心骨呢?答案是肯定的。

結論 從形式上講,Struts2的配置元素的表現形式以XML為核心,而Properties文件則作為另外一種配置形式起到輔助作用。

事實上,除了XML和Properties文件的形式,Struts2對于配置的構成形式并沒有一個明確而死板的規定,因而我們可以在很多Struts2的擴展中見到類似Annotation或者“約定大于配置”的配置形式。有關這一點,我們在后續的章節會詳細展開。

3.4.1.3 配置的作用范圍

在表3-6中,我們可以看到不同的配置文件有著不同的作用范圍。

在這其中,struts-default.xml和default.properties是框架級別的配置文件。這兩個文件蘊含在Struts2的核心JAR包之中,它們將在應用程序啟動時被Struts2的初始化程序讀取并加載。

在應用級別,Struts2提供了2個與框架級別的配置文件相對應的配置文件:struts. xml和struts.properties。它們的結構與框架級別的配置文件完全相同,但是其中定義的所有內容將覆蓋框架級別的配置定義,從而為程序員提供進行應用級別配置擴展的基本方法。

除此之外,我們還可以通過Struts2的插件來進行應用級別的配置定義,這一配置定義在插件所在JAR包的根目錄,并以struts-plugin.xml的文件名形式出現。這種插件形式的配置定義則從另外一個角度為程序員提供了足夠多的配置擴展層次。

配置的作用范圍從一定程度上應該與配置文件的加載順序結合起來看。有興趣的讀者可以在本書的第12章找到相關內容。

3.4.1.4 配置文件的必要性

在配置文件的這些特性中,有一點值得我們注意:所有我們提到的應用級別的配置文件,都不是必須存在的。

因為在默認情況下,Struts2框架級別的配置文件已經足以支撐起一個Struts2的應用了。因而,在表3-6所列出的應用級別的配置文件中,只有web.xml中的配置是必需的。正如前面介紹的,因為我們需要在web.xml中定義Struts2的入口程序。這也是驅動整個Struts2的兩條主線和兩個階段運行的核心所在。缺了web.xml的配置,Struts2本身也就無從談起了。

3.4.1.5 配置元素的邏輯意義

雖然XML配置文件和Properties配置文件都是Struts2所支持的配置文件形式,但是從內容上說,XML包含了所有Struts2內置對象的定義、運行參數的定義、結構化配置定義、事件響應映射關系定義等所有Struts2運行必不可少的運行元素;而Properties文件呢,主要用于指定Struts2的運行參數。因此,我們可以得出一個結論:

結論 Struts2框架中的XML文件的配置元素定義是Properties文件的配置元素定義的超集。

也就是說,凡是能夠在Properties文件中定義的配置元素,我們都可以在XML中找到相應的配置方式代替,反之則不成立。因而,要研究Struts2的微觀構成元素,我們可以從分析Struts2的XML配置文件的元素入手。

3.4.2 Struts2配置元素定義

根據3.4.1節中我們對Struts2配置元素的分析所得到的結論,對Struts2所有配置元素的研究,可以從位于Struts2核心JAR包中的struts-default.xml文件入手,struts-default. xml部分源碼如代碼清單3-2所示。

代碼清單3-2 struts-default.xml

          <!DOCTYPE struts PUBLIC
              "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
              "http://struts.apache.org/dtds/struts-2.1.7.dtd">
          <struts>
              <bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" />
              <bean  type="com.opensymphony.xwork2.ObjectFactory"  name="struts"
    class="org.apache.struts2.impl.StrutsObjectFactory" />
          // 這里省略了許多bean定義
          <constant name="struts.multipart.handler" value="jakarta" />
              <package name="struts-default" abstract="true">
                <result-types>
                  <result-type name="chain"
          class="com.opensymphony.xwork2.ActionChainResult"/>
                    <result-type name="dispatcher"
          class="org.apache.struts2.dispatcher.ServletDispatcherResult"
          default="true"/>
                // 這里省略了許多result-type定義
                </result-types>
                <interceptors>
                    <interceptor name="alias"
          class="com.opensymphony.xwork2.interceptor.AliasInterceptor"/>
                    <interceptor name="autowiring"
          class="com.opensymphony.xwork2.spring.interceptor.
          ActionAutowiringInterceptor"/>
                    <interceptor name="chain"
          class="com.opensymphony.xwork2.interceptor.ChainingInterceptor"/>
                    // 這里省略了許多interceptor定義
                    <!-- Basic stack -->
                    <interceptor-stack name="basicStack">
                        <interceptor-ref name="exception"/>
                        <interceptor-ref name="servletConfig"/>
                        <interceptor-ref name="prepare"/>
                        <interceptor-ref name="checkbox"/>
                        <interceptor-ref name="multiselect"/>
                        <interceptor-ref name="actionMappingParams"/>
                        <interceptor-ref name="params">
                            <param
          name="excludeParams">dojo\..*,^struts\..*</param>
                        </interceptor-ref>
                        <interceptor-ref name="conversionError"/>
                    </interceptor-stack>
                    // 這里省略了許多interceptor-stack定義
                </interceptors>
                <default-interceptor-ref name="defaultStack"/>
                <default-class-ref class="com.opensymphony.xwork2.ActionSupport" />
              </package>
          </struts>

我們在這里為了顯示XML配置文件的結構,省略了其中某些具體節點的定義。不過這絲毫不影響我們對XML配置中的一些主要節點進行研究。

3.4.2.1 include節點

include節點本身并沒有在struts-default.xml中出現,但它卻是Struts2配置文件所支持的第一層根節點之一。include節點的主要作用是幫助我們管理Struts2的配置文件,實現配置文件的模塊化。

include節點最為常見的使用方法,是我們可以在應用級別的配置文件(struts.xml)里,將整個應用的配置根據一定的邏輯劃分成若干個獨立的配置文件,以方便進行模塊化管理和團隊開發。如代碼清單3-3所示是一個典型的例子。

代碼清單3-3 struts.xml

        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE struts PUBLIC
            "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
            "http://struts.apache.org/dtds/struts-2.0.dtd">
        <struts>
            <include file="web/struts-config.xml" />
            <include file="web/struts-system.xml" />
            <include file="web/struts-user.xml" />
        </struts>

從上面的例子中,我們可以看到Struts2進行配置模塊化的管理方式:利用結構完全相同的文件,通過include節點把這些文件串聯起來。這是一個非常直觀有效的管理方式,這種模式有點類似于Java語法中的“對象引用嵌套”。其最大的好處就在于方便理解,并且能夠支持團隊化開發,團隊成員只需要關心他自身所工作的模塊,從而避免了配置層面的沖突發生。

另外一種進行配置模塊化管理的方式是使用“繼承”機制。這種機制我們會在之后對package節點的分析中有所涉及,讀者可以細細體會它們兩者的不同之處。

3.4.2.2 bean節點

bean節點在struts-default.xml中廣泛用于定義Struts2框架級別的內置對象。我們可以從bean節點的屬性定義中發現這個節點實際上是一個用于描述接口及其實現類映射關系的節點。

在邏輯關系上,bean節點的尋址是通過name屬性和type屬性共同構成一個邏輯主鍵來共同決定一個class屬性。也就是說,我們可以通過name屬性和type屬性的值來控制一個接口的不同實現方式,這也是Struts2用來切換程序運行機制的最基本方式。

這些bean節點在Struts2內部是以何種形式存在的呢?Struts2在框架級別實現了一個對象容器,并將配置文件中所有bean節點所定義的對象納入容器之中進行管理。Struts2通過這個容器在框架級別負責這些對象的創建、銷毀以及依賴關系的處理。熟悉Spring框架的讀者會發現,在這里,這個容器的概念與Spring中容器的概念非常類似,都是用來管理程序運行過程中對象的生命周期的。Struts2不僅實現了一個容器,并且還在容器的基礎之上實現了依賴注入(IoC),從而使得所有Struts2中所定義的對象都可以被有條不紊地創建、執行和銷毀。

在應用級別的配置文件中,通過增加bean節點,就可以把一個對象納入Struts2的容器中進行管理。此時,我們可以通過Struts2提供的容器訪問接口(原生API方式)或者依賴注入的方式,獲取我們自定義的這個bean對象并使用。

3.4.2.3 constant節點

constant節點從結構上看是一個非常典型的鍵值對類型的配置,主要用于定義Struts2運行時的參數。在struts-default.xml中,我們很少看到constant節點的定義,因為Struts2主要使用了Properties文件來定義運行時的參數而并非將它們放在XML中。這實際上符合了“讓最合適的表現形式來表達最合適的配置語義”的設計原則。

constant節點的作用與Struts2的Properties文件的配置形式是完全重合的。這一點有助于我們理解Struts2所支持的XML配置形式實際上是Properties文件配置形式的超集這一結論。因而,constant節點也成為兩種配置形式在邏輯上的交集。

constant節點中所有運行時的參數定義與bean節點一樣,也會在系統初始化時被加載到Struts2的容器中進行統一管理。也就是說,Struts2的容器不僅僅負責對Struts2所有內置對象的管理,還要負責對系統的運行參數進行管理。Struts2將兩者進行統一的主要好處在于Struts2在內部對與這些框架相關的運行對象或者運行參數可以一視同仁地進行處理,通過統一的容器訪問接口(或者依賴注入的方式),Struts2可以從容器中方便地組織起整個程序的架構。這種統一性可大大降低編程的復雜度,使Struts2的運行機制更為順暢。

3.4.2.4 package節點

package節點是一個復雜的復合節點,在其中包含了眾多子節點:ResultType、Interceptor、InterceptorStack、Action,等等。實際上,這些子節點全部繼承自XWork框架。還記得第2章中我們對XWork框架的比喻嗎?XWork就像一條生產流水線,定義了一系列事件執行的步驟和次序。一個package節點實際上可以被看作是一條簡單的XWork生產流水線,其中包含XWork框架如何應對某些請求并選擇相應的執行序列進行處理的具體方式。其中的ResultType、Interceptor子節點和Action子節點也正是我們之前提到過的XWork中最為重要的微觀元素,也是構成事件執行序列的主要元素。

從功能上分析,package節點與之前的bean節點和constant節點有著本質的區別。無論是bean節點還是constant節點,它們都與框架自身的運行狀態有關,并且被Struts2內部的容器所管理。而package節點的作用卻是定義一種映射關系,更多反映了框架如何與外部程序進行交互的過程。我們將在后續章節展開分析這些節點的實際作用,讀者在這里只需要大致了解package節點的構成要素即可。

在package節點的屬性之中,name是一個唯一的標識符,namespace則從命名空間的角度為整個事件請求機制劃分不同的種類。在運行期,我們可以認為name屬性和namespace屬性都用于對請求進行邏輯劃分。

package節點中的extends屬性允許package和package之間形成相應的繼承關系。通過繼承,子package自動獲得父package的所有配置定義。extends屬性則從另外一個角度,允許開發人員進行配置的模塊化管理,抽取公共的配置定義成為一個公共的父package,子package可以任意引用父package中的任何配置定義,這極大地簡化了配置的工作量。讀者在這里可以體會“繼承”和“引用”這兩種不同的擴展模式在Struts2配置文件中的應用。

3.4.3 Struts2配置元素的分類

在對struts-default.xml的分析中,我們實際上已經提供了在邏輯上對Struts2中的配置元素進行分類的思路:從節點所表達的邏輯含義和節點在程序中所起的作用對配置元素進行分類。

XML配置文件中的bean節點和constant節點,它們其中一個是構成程序運行的對象,而另一個用于指定程序運行的執行參數。它們都與Struts2自身的運行機制有關,并且有一個統一的管理機制,將它們歸為同一類的配置元素應該毫無異議。習慣上,我們把這類配置元素稱之為“容器配置元素”。有關這個命名的具體由來,我們將在之后的章節詳細解釋。

XML配置文件中的package節點定義了一種事件請求響應的映射關系,反映的是Struts2對于外部事件請求時如何進行響應的處理序列,它與bean節點和constant節點在整個框架中起的作用完全不同,是自成體系的另一類配置元素。對于這一類配置元素,我們通常稱之為“事件映射關系”。

在這里我們需要強調的是,Struts2的配置元素的定義體系與XWork框架是一脈相承的。無論是節點定義還是節點的邏輯關系,Struts2都直接從XWork框架繼承而來。

明確Struts2對于配置元素的分類,可為整個框架進行配置元素的對象化打下堅實的基礎。在之后的章節中,我們可以看到Struts2針對不同的配置元素,定義了與之對應的數據結構和處理方法。

3.5 小結

在本章中,我們從Struts2的歷史談起,對Struts2的外部環境和內部構成機理都做了簡要的分析,目的是使讀者對Struts2框架有個初步的認識。其中,對Struts2的外部環境的介紹有助于使讀者從技術的角度了解Struts2的適用范圍及與其依賴的項目之間的聯系。而對Struts2內部運行主線和構成元素的剖析則可幫助讀者對Struts2自身的架構有所掌握。

本章的內容對于全書有著提綱挈領的作用,尤其是對Struts2運行的邏輯主線的分析,將成為我們研究Struts2內在機理的切入點。因而,本章中的許多概念和結論會在之后的分析講解中被反復提及。讀者應謹記這些結論,并與之后的章節對應起來,體會Struts2的總體架構。

回顧本章,大家可以重新思考下面的這些問題,是否在本章中得到了答案呢?

Struts2和Struts1.X有什么區別?它們各自的發展軌跡又如何?

Struts2依賴于哪些核心技術?

Struts2可以應用在什么樣的項目中?

Struts2可以分成哪兩條邏輯運行主線?

Struts2在處理Http請求時,可以分成哪兩個主要階段?

Struts2通過哪些元素的相互配合來完成初始化運行主線?

XWork框架主要由哪些元素構成?它們之間有什么關系?

Struts2有哪些配置表現形式?

Struts2中的配置元素可以分為哪兩個大類?

什么是配置元素的對象化過程?

Struts2的配置元素的對象化過程由哪兩大元素配合完成?

主站蜘蛛池模板: 怀来县| 鹰潭市| 太和县| 前郭尔| 唐海县| 定安县| 浙江省| 安龙县| 多伦县| 长葛市| 保亭| 克山县| 西乌珠穆沁旗| 大英县| 筠连县| 昭通市| 柳江县| 称多县| 钟祥市| 页游| 外汇| 贵阳市| 沙湾县| 大新县| 本溪市| 临猗县| 沙田区| 溧水县| 红桥区| 克拉玛依市| 阳西县| 邵阳县| 通榆县| 郑州市| 米林县| 崇礼县| 吉隆县| 兴隆县| 峡江县| 五寨县| 图们市|