Struts2教程

第一章 搭建Struts2開發環境

   Struts的官方網站上,寫着下面兩段話:css

Apache Struts 2 is an elegant, extensible framework for creating enterprise-ready Java web applications. The framework is designed to streamline the full development cycle, from building, to deploying, to maintaining applications over time.html

Apache Struts 2 was originally known as WebWork 2. After working independently for several years, the WebWork and Struts communities joined forces to create Struts2. This new version of Struts is simpler to use and closer to how Struts was always meant to be.java

其大意爲:Apache Struts2是一個爲企業級應用打造的優秀的、可擴展的WEB框架,該框架旨在充分精簡應用程序的開發週期,從而減小建立、發佈直到應用所花費的時間。程序員

Apache Struts2本來就是聞名中外的Webwork2,在各自經歷幾年的發展以後,StrutsWebWork社區決定合二爲一,也就是今天的Struts2web

Struts是一個基於Model2MVC框架,爲應用程序的WEB層提供了良好的結構嚴謹的實現。Struts發展較早,早期的Struts1.X已被不少J2EE程序員熟悉,通過多年來的發展,這支隊伍變得愈來愈大,不少企業級應用程序都是基於Struts開發的。apache

Struts2Struts1.X已經不能再放到一塊兒比較,雖然都是對MVC架構模式的實現,本質卻徹底不一樣。Struts2的前身是WebWork,其實現方式和功能都要優於Struts1.X,可是,Struts先入爲主,不少應用程序都基於Struts,其生命力和普及度使得WebWork落於下風。隨着新思想和新架構的不斷涌入,特別是WEB2.0被大量說起,Struts1.x顯然沒法跟上突飛猛進的變化,在不少應用上顯得力不從心,最終催生了Struts2.0。能夠說Struts2.0是爲變而變。編程

很大程度上,Struts2.0沒法避開投機取巧的嫌疑。不過,藉助Struts的名聲,加上WebWork構建良好的框架,兩者取長補短,確實不失爲一種黃金組合和一種絕佳的宣傳方式。數組

筆者杜撰此文時,能夠下載到的最新版本爲2.1.0,但他的魅力已初露尖角,應該會有很好的前途。瀏覽器

Struts2的新特徵

若是讀者熟悉Struts1.X,會發現Struts2Struts1.X有了巨大的變化:安全

Action :

 Struts1要求Action類繼承一個抽象基類。Struts1的一個廣泛問題是使用抽象類編程而不是接口。

 Struts 2 Action類能夠實現一個Action接口,也可實現其餘接口,使可選和定製的服務成爲可能。Struts2提供一個ActionSupport基類去實現經常使用的接口。Action接口不是必須的,任何有execute標識的POJO對象均可以用做Struts2Action對象。

線程模式:

 Struts1 Action是單例模式而且必須是線程安全的,由於僅有Action的一個實例來處理全部的請求。單例策略限制了Struts1 Action能做的事,而且要在開發時特別當心。Action資源必須是線程安全的或同步的。

 Struts2 Action對象爲每個請求產生一個實例,所以沒有線程安全問題。(實際上,servlet容器給每一個請求產生許多可丟棄的對象,而且不會致使性能和垃圾回收問題)

Servlet 依賴 

 Struts1 Action 依賴於Servlet API ,由於當一個Action被調用時HttpServletRequest  HttpServletResponse 被傳遞給execute方法。

 Struts 2 Action不依賴於容器,容許Action脫離容器單獨被測試。若是須要,Struts2 Action仍然能夠訪問初始的requestresponse。可是,其餘的元素減小或者消除了直接訪問HttpServetRequest  HttpServletResponse的必要性。

可測性:

 測試Struts1 Action的一個主要問題是execute方法暴露了servlet API(這使得測試要依賴於容器)。一個第三方擴展--Struts TestCase--提供了一套Struts1的模擬對象(來進行測試)。

 Struts 2 Action能夠經過初始化、設置屬性、調用方法來測試,「依賴注入」支持也使測試更容易。

捕獲輸入:

 Struts1 使用ActionForm對象捕獲輸入。全部的ActionForm必須繼承一個基類。由於其餘JavaBean不能用做ActionForm,開發者常常建立多餘的類捕獲輸入。動態BeanDynaBeans)能夠做爲建立傳統ActionForm的選擇,可是,開發者多是在從新描述(建立)已經存在的JavaBean(仍然會致使有冗餘的javabean)。

 Struts 2直接使用Action屬性做爲輸入屬性,消除了對第二個輸入對象的需求。輸入屬性多是有本身()屬性的rich對象類型。Action屬性可以經過web頁面上的taglibs訪問。Struts2也支持ActionForm模式。rich對象類型,包括業務對象,可以用做輸入/輸出對象。這種ModelDriven 特性簡化了taglibPOJO輸入對象的引用。

表達式語言:

 Struts1 整合了JSTL,所以使用JSTL EL。這種EL有基本對象圖遍歷,可是對集合和索引屬性的支持很弱。

 Struts2可使用JSTL,可是也支持一個更強大和靈活的表達式語言--"Object Graph Notation Language" (OGNL). 

綁定值到頁面(view:

 Struts 1使用標準JSP機制把對象綁定到頁面中來訪問。

 Struts 2 使用 "ValueStack"技術,使taglib可以訪問值而不須要把你的頁面(view)和對象綁定起來。ValueStack策略容許經過一系列名稱相同但類型不一樣的屬性重用頁面(view)。

類型轉換:

 Struts 1 ActionForm 屬性一般都是String類型。Struts1使用Commons-Beanutils進行類型轉換。每一個類一個轉換器,對每個實例來講是不可配置的。

 Struts2 使用OGNL進行類型轉換。提供基本和經常使用對象的轉換器。

校驗:  

 Struts 1支持在ActionFormvalidate方法中手動校驗,或者經過Commons Validator的擴展來校驗。同一個類能夠有不一樣的校驗內容,但不能校驗子對象。

 Struts2支持經過validate方法和XWork校驗框架來進行校驗。XWork校驗框架使用爲屬性類類型定義的校驗和內容校驗,來支持chain校驗子屬性 

Action執行的控制:

 Struts1支持每個模塊有單獨的Request Processors(生命週期),可是模塊中的全部Action必須共享相同的生命週期。

 Struts2支持經過攔截器堆棧(Interceptor Stacks)爲每個Action建立不一樣的生命週期。堆棧可以根據須要和不一樣的Action一塊兒使用。

注:以上資料從網上搜集,來源:Struts開發組,翻譯:tianxinet(胖猴)。

Struts2的環境要求

       Apache Struts2的環境需求以下:

              Servlet API 2.4

JSP API 2.0

Java 5

須要提醒的是,在Struts中會用到Annotation,因此請將JDK版本升級到1.5.

Struts2環境搭建

4.1Struts的下載

       從遊覽器輸入http://people.apache.org/builds/struts/,便可看到Struts的各個版本列表。從下圖中能夠發現,如今Struts2.0的最新版是2.1.0,發佈於20071029           

       

(圖1

       

       (2)

從圖2中能夠看出,便可以分開下載,又能夠一次所有下載。所有下載的大小爲83M,

       下表註明了各個壓縮包的做用。

壓縮包名稱

做用

struts-2.1.0-docs.zip 

文檔,包含了Struts2API

struts-2.1.0-lib.zip 

構建Struts2工程所須要的包

struts-2.1.0-src.zip 

Struts2的全部源代碼

struts2-blank-2.1.0.war

空白工程

struts-2.1.0-all.zip

大集成,包括上面全部的內容

4.2 開發工具介紹

       目前J2EE開發工具主要分爲EclipseNetBeans兩大陣營,Eclipse的最高版本爲3.3,NetBeans的最高版本爲6.0.今天剛剛重新聞上看到,NetBeans6.0的英文正式版正式發佈了,真是可喜可賀。

       筆者在開發時以Eclipse爲主,但Eclipse並不支持WEB開發,須要安裝相應插件。MyEclipse是一個功能強大且框架支持很是普遍的WEB開發插件,該產品是收費項目。目前MyEclipse的最高版本爲6.0,即使如此,尚不支持Struts2.0,咱們只能手工配置Struts2.0的開發環境。

4.3 庫文件

       從網站上下載的Struts2包含了二三十個庫文件,但大多數是可選的,有些庫是插件,用於和其餘框架的整合。

       讀者可自行下載struts2-blank-2.1.0.war壓縮包,展開後是一個很是簡單的項目,從WEB-INF/lib目錄中能夠看到5個庫文件,解釋以下:

包名

說明

commons-logging-1.0.4.jar

日誌管理

freemarker-2.3.8.jar

表現層框架,定義了struts2的可視組件主題(theme

ognl-2.6.11.jar

OGNL表達式語言,struts2支持該EL

struts2-core-2.0.10.jar

struts2的核心庫

xwork-2.0.4.jar

webwork的核心庫,天然須要它的支持

 

       (圖3

4.3 使用Eclipse搭建Struts2的開發環境

4.3.1建立用戶庫

       Struts2所需的包建成用戶庫,能夠更加方便地進行管理和使用,這是一個好的習慣——編程從習慣開始。

       1.選擇菜單Window->Preferences->Java->Build Path->User Libraries。如圖4

       

       (4)

       2.點擊右側的New…按鈕,建立一個新的用戶庫,彈出如圖5所示對話框:

       

       (5)


       3.輸入用戶庫的名稱,如:Struts2,點擊OK按鈕,該對話框自動關閉。結果如圖6所示:

       

       (圖6

       此時,右側的按鈕被點亮。

       4.點擊「Add JARS…」按鈕,添加用戶庫所需的庫文件,在Struts2中,至少要包含上文中提到的5個庫文件。添加後效果如圖7所示:

       

       (圖7

       5.點擊「OK」完成。

4.3.2開發第一個Struts2應用程序——世界,你好

       開發WEB應用程序,本文使用了MyEclipse插件。該插件爲收費軟件,目前提供英文版和日文版,不一樣的版本能夠運行在WindowsLinux等操做系統上。爲了方便用戶,MyEclipse有一個Full版,連同Eclipse一塊兒安裝,對於初學者而言,能夠減小不少麻煩和困擾。

       讀者可自行去http://www.myeclipseide.com/網站下載該軟件的共享版本。建議讀者下載MyEclipse5.5(這也是筆者使用的版本),這個版本相對比較穩定,MyEclipse6.0還處於測試之中。

       入門教程老是以HelloWorld做爲學習的第一步,天然筆者也不例外。本示例從遊覽器輸入網址,提交請求後在頁面中顯示「世界,你好」的信息。

       1.新建WEB工程,如圖8所示:

       

       (圖8

       2.點擊「Next」,輸入工程名,如圖9所示:

       

       (圖9

       3.點擊「Finish」完成。

       4.如今將Struts2的庫導入到工程中,右擊工程名稱彈出快捷菜單,選擇Build Path->Add Libraries…,如圖10所示。

       

       (圖10

       5.從彈出的對話框中選擇「User Libraries」,如圖11所示。

       

       (圖11

       6. 單擊下一步,咱們看到,上文中建立的用戶庫出如今列表中,在「Struts2」前的複選框上打勾,點擊「Finish」完成。如圖12

       

       (圖12

       7.Struts2所帶的過濾器org.apache.struts2.dispatcher.FilterDispatcher配置到工程的web.xml文件中,默認狀況下,該過濾器攔截請求字符串中以.action結尾的請求,並將該請求委託給指定的Action進行處理。最直觀的表現就是調用Actionexecute()方法。代碼以下:

代碼清單1web.xml

   <filter>

       <filter-name>struts2</filter-name>

       <filter-class>

           org.apache.struts2.dispatcher.FilterDispatcher

       </filter-class>

    </filter>

    <filter-mapping>

       <filter-name>struts2</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

注:在Sturts1.X中,該行爲由Servlet完成。

8.建立包com.lizanhong.action,並在該包中建立HelloWorldAction類,該類繼承自com.opensymphony.xwork2.ActionSupport。理論上,Action能夠不繼承任何類或實現任何接口,以加強程序的可測試性,這也是和Struts1.X不一樣的地方。可是,繼承自ActionSupport能夠減小更多的編碼工做。

       ActionSupport中,定義了方法execute(),當用戶向該Action發送請求時,會自動調用。程序代碼以下:

代碼清單2HelloWorldAction.java

package com.lizanhong.action;

import com.opensymphony.xwork2.ActionSupport;

publicclass HelloWorldAction extends ActionSupport {

    @Override

    public String execute() throws Exception {

       System.out.println("Action執行了。");

       returnSUCCESS;

    }

}

注:ActionSupportStruts2提供的類,功能相似於Struts1.x中的Action類,該類封裝了幾個有用的功能,好比:

getText():從資源文件中獲取國際化消息。

addFieldError():驗證輸入未經過時添加錯誤消息,支持國際化。

execute():該方法通常會被重寫,當客戶端向Action發送請求時,會調用此方法。

總結起來,該類主要提供了錯誤消息的支持和國際化支持。

       在工程類路徑下建立struts.xml文件,這是Struts2的配置文件,相似於Struts1.x中的struts-config.xml,在struts.xml文件中能夠配置ActionBeanInterceptor等組件。

代碼清單3struts.xml

<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN""http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

    <include file="struts-default.xml"></include>

   

    <package name="a" extends="struts-default">

       <action name="helloworld" class="com.lizanhong.action.HelloWorldAction">

           <result>/result.jsp</result>

       </action>

    </package>

</struts>

注:WEB應用程序的類路徑是指WEB-INF/classes目錄,在Eclipse中,建立在src目錄下的文件最終發佈後會自動複製到WEB-INF/classes目錄下。

代碼清單3中涉及到不少標籤,如下是簡單的解釋:

標籤名稱

說明

include

包含其餘xml文件,在示例中,這意味着struts.xml能夠訪問定義在struts-default.xml文件中的組件。

該元素可使得Struts2定義多個配置文件,「分而治之」。

要注意的是,任何一個struts2配置文件都應該和struts.xml有相同的格式,包括doctype,而且能夠放在類路徑下的任何地方。

package

Action或截攔器分組。

name:名稱,必填項,名稱自定義,沒特別要求。方便別的package引用。

extendspackage能繼承其餘的package,即經過該屬性實現,值爲另外一個packagename

在示例中,extends =」struts-default」是從struts-default.xml中繼承的。

action

定義Actionname屬性爲訪問時用到的名稱,class屬性是Action的類名。

result

根據Action的返回值定義頁面導航。

Action的預約義的返回值有:

String SUCCESS = "success";

String NONE    = "none";

String ERROR   = "error";

String INPUT   = "input";

String LOGIN   = "login";

好比,當Action返回SUCCESS時但願轉到ok.jsp頁面,則能夠這樣寫:

<result name=」success」>ok.jsp</result>

    其中,name的缺省爲success

       9.result.jsp是一個很是簡單的jsp頁面,輸出「世界,你好」。

代碼清單4result.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%

String path = request.getContextPath();

String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";

%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

 <head>

    <base href="<%=basePath%>">

   

    <title>My JSP 'result.jsp' starting page</title>

   

    <meta http-equiv="pragma" content="no-cache">

    <meta http-equiv="cache-control" content="no-cache">

    <meta http-equiv="expires" content="0">   

    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">

    <meta http-equiv="description" content="This is my page">

    <!--

    <link rel="stylesheet" type="text/css" href="styles.css">

    -->

 </head>

 

 <body>

    世界,你好<br>

 </body>

</html>

9.發佈工程,在瀏覽器中輸入:http://localhost:8081/Struts2Demo/helloworld.action,在控制檯輸出「Action執行了。」

10.在瀏覽器的結果以下圖13

 

(13)

struts.xml的定義文件

代碼清單5struts-2.0.dtd

<?xml version="1.0" encoding="UTF-8"?>

<!-- START SNIPPET: strutsDtd -->

<!--

   Struts configuration DTD.

   Use the following DOCTYPE

  

   <!DOCTYPE struts PUBLIC

    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

    "http://struts.apache.org/dtds/struts-2.0.dtd">

-->

<!ELEMENT struts (package|include|bean|constant)*>

<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, global-results?, global-exception-mappings?, action*)>

<!ATTLIST package

    name CDATA #REQUIRED

    extends CDATA #IMPLIED

    namespace CDATA #IMPLIED

    abstract CDATA #IMPLIED

    externalReferenceResolver NMTOKEN #IMPLIED

> 

<!ELEMENT result-types (result-type+)>

<!ELEMENT result-type (param*)>

<!ATTLIST result-type

    name CDATA #REQUIRED

    class CDATA #REQUIRED

    default (true|false"false"

> 

<!ELEMENT interceptors (interceptor|interceptor-stack)+>

<!ELEMENT interceptor (param*)>

<!ATTLIST interceptor

    name CDATA #REQUIRED

    class CDATA #REQUIRED

> 

<!ELEMENT interceptor-stack (interceptor-ref+)>

<!ATTLIST interceptor-stack

    name CDATA #REQUIRED

> 

<!ELEMENT interceptor-ref (param*)>

<!ATTLIST interceptor-ref

    name CDATA #REQUIRED

> 

<!ELEMENT default-interceptor-ref (param*)>

<!ATTLIST default-interceptor-ref

    name CDATA #REQUIRED

> 

<!ELEMENT default-action-ref (param*)>

<!ATTLIST default-action-ref

    name CDATA #REQUIRED

> 

<!ELEMENT global-results (result+)>

<!ELEMENT global-exception-mappings (exception-mapping+)>

<!ELEMENT action (param|result|interceptor-ref|exception-mapping)*>

<!ATTLIST action

    name CDATA #REQUIRED

    class CDATA #IMPLIED

    method CDATA #IMPLIED

    converter CDATA #IMPLIED

> 

<!ELEMENT param (#PCDATA)>

<!ATTLIST param

    name CDATA #REQUIRED

> 

<!ELEMENT result (#PCDATA|param)*>

<!ATTLIST result

    name CDATA #IMPLIED

    type CDATA #IMPLIED

> 

<!ELEMENT exception-mapping (#PCDATA|param)*>

<!ATTLIST exception-mapping

    name CDATA #IMPLIED

    exception CDATA #REQUIRED

    result CDATA #REQUIRED

> 

<!ELEMENT include (#PCDATA)>

<!ATTLIST include

    file CDATA #REQUIRED

> 

<!ELEMENT bean (#PCDATA)>

<!ATTLIST bean

    type CDATA #IMPLIED

    name CDATA #IMPLIED

    class CDATA #REQUIRED

    scope CDATA #IMPLIED

    static CDATA #IMPLIED

    optional CDATA #IMPLIED

> 

<!ELEMENT constant (#PCDATA)>

<!ATTLIST constant

    name CDATA #REQUIRED

    value CDATA #REQUIRED   

> 

<!-- END SNIPPET: strutsDtd -->

總結

       Struts是一個時下很是流行並被許多企業級應用程序採用的WEB框架,Struts2Struts1.x的基礎上進行了大量改造,和WebWork合二爲一,引進了更多的新觀念、新思想和新技術,使之更符合J2EE應用程序開發的須要。

       「工欲善其事,必先利其器」,掌握一兩種開發工具,可以大大提升編程效率,也能加強開發者的信心。學習一門新技術時,第一個應用程序很是重要,若是第一個最簡單的程序運行不成功,會使得學習者的積極性大打折扣,這也是筆者不肯意看到的。因此,本章圖文並茂地詳細介紹了Struts2應用程序的開發過程,並儘量少的說起陌生的概念和術語。

第二章 Struts2的工做機制及分析

概述

本章講述Struts2的工做原理。

讀者若是曾經學習過Struts1.x或者有過Struts1.x的開發經驗,那麼千萬不要想固然地覺得這一章能夠跳過。實際上Struts1.x與Struts2並沒有咱們想象的血緣關係。雖然Struts2的開發小組極力保留Struts1.x的習慣,但由於Struts2的核心設計徹底改變,從思想到設計到工做流程,都有了很大的不一樣。

Struts2是Struts社區和WebWork社區的共同成果,咱們甚至能夠說,Struts2是WebWork的升級版,他採用的正是WebWork的核心,因此,Struts2並非一個不成熟的產品,相反,構建在WebWork基礎之上的Struts2是一個運行穩定、性能優異、設計成熟的WEB框架。

本章主要對Struts的源代碼進行分析,由於Struts2與WebWork的關係如此密不可分,所以,讀者須要下載xwork的源代碼,訪問http://www.opensymphony.com/xwork/download.action便可自行下載。

下載的Struts2源代碼文件是一個名叫struts-2.1.0-src.zip的壓縮包,裏面的目錄和文件很是多,讀者能夠定位到struts-2.1.0-src"struts-2.0.10"src"core"src"main"java目錄下查看Struts2的源文件,如圖14所示。

 

(圖14)

主要的包和類

Struts2框架的正常運行,除了佔核心地位的xwork的支持之外,Struts2自己也提供了許多類,這些類被分門別類組織到不一樣的包中。從源代碼中發現,基本上每個Struts2類都訪問了WebWork提供的功能,從而也能夠看出Struts2與WebWork千絲萬縷的聯繫。但不管如何,Struts2的核心功能好比將請求委託給哪一個Action處理都是由xwork完成的,Struts2只是在WebWork的基礎上作了適當的簡化、增強和封裝,並少許保留Struts1.x中的習慣。

如下是對各包的簡要說明:

包名

說明

org.apache.struts2. components

該包封裝視圖組件,Struts2在視圖組件上有了很大增強,不只增長了組件的屬性個數,更新增了幾個很是有用的組件,如updownselect、doubleselect、datetimepicker、token、tree等。

另外,Struts2可視化視圖組件開始支持主題(theme),缺省狀況下,使用自帶的缺省主題,若是要自定義頁面效果,須要將組件的theme屬性設置爲simple。

org.apache.struts2. config

該包定義與配置相關的接口和類。實際上,工程中的xml和properties文件的讀取和解析都是由WebWork完成的,Struts只作了少許的工做。

org.apache.struts2.dispatcher

Struts2的核心包,最重要的類都放在該包中。

org.apache.struts2.impl

該包只定義了3個類,他們是StrutsActionProxy、StrutsActionProxyFactory、StrutsObjectFactory,這三個類都是對xwork的擴展。

org.apache.struts2.interceptor

定義內置的截攔器。

org.apache.struts2.util

實用包。

org.apache.struts2.validators

只定義了一個類:DWRValidator。

org.apache.struts2.views

提供freemarker、jsp、velocity等不一樣類型的頁面呈現。

下表是對一些重要類的說明:

類名

說明

org.apache.struts2.dispatcher. Dispatcher

       該類有兩個做用:

       一、初始化

       二、調用指定的Action的execute()方法。

org.apache.struts2.dispatcher. FilterDispatcher

       這是一個過濾器。文檔中已明確說明,若是沒有經驗,配置時請將url-pattern的值設成/*。

       該類有四個做用:

       一、執行Action

       二、清理ActionContext,避免內存泄漏

       三、處理靜態內容(Serving static content)

       四、爲請求啓動xwork’s的截攔器鏈。

com.opensymphony.xwork2. ActionProxy

       Action的代理接口。

com.opensymphony.xwork2. ctionProxyFactory

       生產ActionProxy的工廠。

com.opensymphony.xwork2.ActionInvocation

       負責調用Action和截攔器。

com.opensymphony.xwork2.config.providers. XmlConfigurationProvider

       負責Struts2的配置文件的解析。

Struts2的工做機制

3.1Struts2體系結構圖

       Strut2的體系結構如圖15所示:

 

       (圖15)

3.2Struts2的工做機制

       從圖15能夠看出,一個請求在Struts2框架中的處理大概分爲如下幾個步驟:

一、客戶端初始化一個指向Servlet容器(例如Tomcat)的請求;

二、這個請求通過一系列的過濾器(Filter)(這些過濾器中有一個叫作ActionContextCleanUp的可選過濾器,這個過濾器對於Struts2和其餘框架的集成頗有幫助,例如:SiteMesh Plugin);

三、接着FilterDispatcher被調用,FilterDispatcher詢問ActionMapper來決定這個請求是否須要調用某個Action;

四、若是ActionMapper決定須要調用某個Action,FilterDispatcher把請求的處理交給ActionProxy;

五、ActionProxy經過Configuration Manager詢問框架的配置文件,找到須要調用的Action類;

六、ActionProxy建立一個ActionInvocation的實例。

七、ActionInvocation實例使用命名模式來調用,在調用Action的過程先後,涉及到相關攔截器(Intercepter)的調用。

八、一旦Action執行完畢,ActionInvocation負責根據struts.xml中的配置找到對應的返回結果。返回結果一般是(但不老是,也多是另外的一個Action鏈)一個須要被表示的JSP或者FreeMarker的模版。在表示的過程當中可使用Struts2 框架中繼承的標籤。在這個過程當中須要涉及到ActionMapper。

注:以上步驟參考至網上,具體網址已忘記。在此表示感謝!

3.3Struts2源代碼分析

       和Struts1.x不一樣,Struts2的啓動是經過FilterDispatcher過濾器實現的。下面是該過濾器在web.xml文件中的配置:

代碼清單6:web.xml(截取)

    <filter>

       <filter-name>struts2</filter-name>

       <filter-class>

           org.apache.struts2.dispatcher.FilterDispatcher

       </filter-class>

    </filter>

    <filter-mapping>

       <filter-name>struts2</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

       Struts2建議,在對Struts2的配置尚不熟悉的狀況下,將url-pattern配置爲/*,這樣該過濾器將截攔全部請求。

       實際上,FilterDispatcher除了實現Filter接口之外,還實現了StrutsStatics接口,繼承代碼以下:

代碼清單7FilterDispatcher結構

publicclass FilterDispatcher implements StrutsStatics, Filter {

}

StrutsStatics並無定義業務方法,只定義了若干個常量。Struts2對經常使用的接口進行了從新封裝,好比HttpServletRequestHttpServletResponseHttpServletContext等。 如下是StrutsStatics的定義:

代碼清單8StrutsStatics.java

publicinterface StrutsStatics {

    /**

     *ConstantfortheHTTPrequestobject.

     */

    publicstaticfinal String HTTP_REQUEST ="com.opensymphony.xwork2.dispatcher.HttpServletRequest";

    /**

     *ConstantfortheHTTPresponseobject.

     */

    publicstaticfinal String HTTP_RESPONSE ="com.opensymphony.xwork2.dispatcher.HttpServletResponse";

    /**

     *ConstantforanHTTPrequest dispatcher}.

     */

    publicstaticfinal String SERVLET_DISPATCHER ="com.opensymphony.xwork2.dispatcher.ServletDispatcher";

    /**

     *Constantfortheservlet context}object.

     */

    publicstaticfinal String SERVLET_CONTEXT ="com.opensymphony.xwork2.dispatcher.ServletContext";

    /**

     *ConstantfortheJSPpage context}.

     */

publicstaticfinal String PAGE_CONTEXT = "com.opensymphony.xwork2.dispatcher.PageContext";

    /**ConstantforthePortletContextobject*/

    publicstaticfinal String STRUTS_PORTLET_CONTEXT = "struts.portlet.context";

}

    容器啓動後,FilterDispatcher被實例化,調用init(FilterConfig filterConfig)方法。該方法建立Dispatcher類的對象,而且將FilterDispatcher配置的初始化參數傳到對象中(詳情請參考代碼清單10),並負責Action的執行。而後獲得參數packages,值得注意的是,還有另外三個固定的包和該參數進行拼接,分別是org.apache.struts2.statictemplate、和org.apache.struts2.interceptor.debugging,中間用空格隔開,通過解析將包名變成路徑後存儲到一個名叫pathPrefixes的數組中,這些目錄中的文件會被自動搜尋。

代碼清單9FilterDispatcher.init()方法

    publicvoid init(FilterConfig filterConfig) throws ServletException {

        this.filterConfig = filterConfig;      

        dispatcher = createDispatcher(filterConfig);

        dispatcher.init();      

        String param = filterConfig.getInitParameter("packages");

        String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging";

        if (param != null) {

            packages = param + " " + packages;

        }

        this.pathPrefixes = parse(packages);

}

代碼清單10FilterDispatcher.createDispatcher()方法

    protected Dispatcher createDispatcher(FilterConfig filterConfig) {

        Map<String,String> params = new HashMap<String,String>();

        for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) {

            String name = (String) e.nextElement();

            String value = filterConfig.getInitParameter(name);

            params.put(name, value);

        }

        returnnew Dispatcher(filterConfig.getServletContext(), params);

   }

    當用戶向Struts2發送請求時,FilterDispatcherdoFilter()方法自動調用,這個方法很是關鍵。首先,Struts2對請求對象進行從新包裝,這次包裝根據請求內容的類型不一樣,返回不一樣的對象,若是爲multipart/form-data類型,則返回MultiPartRequestWrapper類型的對象,該對象服務於文件上傳,不然返回StrutsRequestWrapper類型的對象,MultiPartRequestWrapperStrutsRequestWrapper的子類,而這兩個類都是HttpServletRequest接口的實現。包裝請求對象如代碼清單11所示:

代碼清單11FilterDispatcher.prepareDispatcherAndWrapRequest()方法

protectedHttpServletRequest prepareDispatcherAndWrapRequest(

        HttpServletRequest request,

        HttpServletResponse response) throws ServletException {

        Dispatcher du = Dispatcher.getInstance();

        if (du == null) {

            Dispatcher.setInstance(dispatcher);         

            dispatcher.prepare(request, response);

        } else {

            dispatcher = du;

        }       

        try {

            request = dispatcher.wrapRequest(request, getServletContext());

        } catch (IOException e) {

            String message = "Could not wrap servlet request with MultipartRequestWrapper!";

            LOG.error(message, e);

            thrownew ServletException(message, e);

        }

        return request;

}

    request對象從新包裝後,經過ActionMappergetMapping()方法獲得請求的ActionAction的配置信息存儲在ActionMapping對象中,該語句以下:mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());。下面是ActionMapping接口的實現類DefaultActionMappergetMapping()方法的源代碼:

代碼清單12DefaultActionMapper.getMapping()方法

    public ActionMapping getMapping(HttpServletRequest request,

            ConfigurationManager configManager) {

        ActionMapping mapping = new ActionMapping();

        String uri = getUri(request);//獲得請求路徑的URI,如:testAtcion.actiontestAction!method

        uri = dropExtension(uri);//刪除擴展名,默認擴展名爲action,在代碼中的定義是List extensions = new ArrayList() {{ add("action");}};

        if (uri == null) {

            returnnull;

        }

        parseNameAndNamespace(uri, mapping, configManager);//uri變量中解析出Actionnamenamespace

        handleSpecialParameters(request, mapping);//將請求參數中的重複項去掉

       //若是Actionname沒有解析出來,直接返回

        if (mapping.getName() == null) {

            returnnull;

        }

      

       //下面處理形如testAction!method格式的請求路徑

        if (allowDynamicMethodCalls) {

            // handle "name!method" convention.

            String name = mapping.getName();

            int exclamation = name.lastIndexOf("!");//!Action名稱和方法名的分隔符

            if (exclamation != -1) {

                mapping.setName(name.substring(0, exclamation));//提取左邊爲name

                mapping.setMethod(name.substring(exclamation + 1));//提取右邊的method

            }

        }

        return mapping;

    }

    該代碼的活動圖以下:

    

    (圖16

從代碼中看出,getMapping()方法返回ActionMapping類型的對象,該對象包含三個參數:Actionnamenamespace和要調用的方法method

   

    若是getMapping()方法返回ActionMapping對象爲null,則FilterDispatcher認爲用戶請求不是Action,天然另當別論,FilterDispatcher會作一件很是有意思的事:若是請求以/struts開頭,會自動查找在web.xml文件中配置的packages初始化參數,就像下面這樣(注意粗斜體部分)

代碼清單13web.xml(部分)

    <filter>

       <filter-name>struts2</filter-name>

       <filter-class>

           org.apache.struts2.dispatcher.FilterDispatcher

       </filter-class>

       <init-param>

           <param-name>packages</param-name>

           <param-value>com.lizanhong.action</param-value>

       </init-param>

    </filter>

    FilterDispatcher會將com.lizanhong.action包下的文件看成靜態資源處理,即直接在頁面上顯示文件內容,不過會忽略擴展名爲class的文件。好比在com.lizanhong.action包下有一個aaa.txt的文本文件,其內容爲「中華人民共和國」,訪問http://localhost:8081/Struts2Demo/struts/aaa.txt時會有如圖17的輸出:

 

(圖17

查找靜態資源的源代碼如清單14

代碼清單14FilterDispatcher.findStaticResource()方法

    protectedvoid findStaticResource(String name, HttpServletRequest request, HttpServletResponse response) throws IOException {

        if (!name.endsWith(".class")) {//忽略class文件

           //遍歷packages參數

            for (String pathPrefix : pathPrefixes) {

                InputStream is = findInputStream(name, pathPrefix);//讀取請求文件流

                if (is != null) {

                    ……(省略部分代碼)

                    // set the content-type header

                    String contentType = getContentType(name);//讀取內容類型

                    if (contentType != null) {

                        response.setContentType(contentType);//從新設置內容類型

                    }

                  ……(省略部分代碼)

                    try {

                     //將讀取到的文件流以每次複製4096個字節的方式循環輸出

                        copy(is, response.getOutputStream());

                    } finally {

                        is.close();

                    }

                    return;

                }

            }

        }

    }

    若是用戶請求的資源不是以/struts開頭——多是.jsp文件,也多是.html文件,則經過過濾器鏈繼續往下傳送,直到到達請求的資源爲止。

    若是getMapping()方法返回有效的ActionMapping對象,則被認爲正在請求某個Action,將調用Dispatcher.serviceAction(request, response, servletContext, mapping)方法,該方法是處理Action的關鍵所在。上述過程的源代碼如清單15所示。

代碼清單15FilterDispatcher.doFilter()方法

    publicvoid doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throwsIOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;

        HttpServletResponse response = (HttpServletResponse) res;

        ServletContext servletContext = getServletContext();

        String timerKey = "FilterDispatcher_doFilter: ";

        try {

            UtilTimerStack.push(timerKey);

            request = prepareDispatcherAndWrapRequest(request, response);//從新包裝request

            ActionMapping mapping;

            try {

                mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());//獲得存儲Action信息的ActionMapping對象

            } catch (Exception ex) {

               ……(省略部分代碼)

                return;

            }

            if (mapping == null) {//若是mappingnull,則認爲不是請求Action資源

                 String resourcePath = RequestUtils.getServletPath(request);

                if ("".equals(resourcePath) && null != request.getPathInfo()) {

                    resourcePath = request.getPathInfo();

                }

              //若是請求的資源以/struts開頭,則看成靜態資源處理

                if (serveStatic && resourcePath.startsWith("/struts")) {

                    String name = resourcePath.substring("/struts".length());

                    findStaticResource(name, request, response);

                } else {

                    //不然,過濾器鏈繼續往下傳遞

                    chain.doFilter(request, response);

                }

                // The framework did its job here

                return;

            }

           //若是請求的資源是Action,則調用serviceAction方法。

            dispatcher.serviceAction(request, response, servletContext, mapping);

        } finally {

            try {

                ActionContextCleanUp.cleanUp(req);

            } finally {

                UtilTimerStack.pop(timerKey);

            }

        }

    }

   

    這段代碼的活動圖如圖18所示:

 

(圖18

    Dispatcher.serviceAction()方法中,先加載Struts2的配置文件,若是沒有人爲配置,則默認加載struts-default.xmlstruts-plugin.xmlstruts.xml,而且將配置信息保存在形如com.opensymphony.xwork2.config.entities.XxxxConfig的類中。

    com.opensymphony.xwork2.config.providers.XmlConfigurationProvider負責配置文件的讀取和解析,addAction()方法負責讀取<action>標籤,並將數據保存在ActionConfig中;addResultTypes()方法負責將<result-type>標籤轉化爲ResultTypeConfig對象;loadInterceptors()方法負責將<interceptor>標籤轉化爲InterceptorConfi對象;loadInterceptorStack()方法負責將<interceptor-ref>標籤轉化爲InterceptorStackConfig對象;loadInterceptorStacks()方法負責將<interceptor-stack>標籤轉化成InterceptorStackConfig對象。而上面的方法最終會被addPackage()方法調用,將所讀取到的數據聚集到PackageConfig對象中,細節請參考代碼清單16

代碼清單16XmlConfigurationProvider.addPackage()方法

    protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {

        PackageConfig newPackage = buildPackageContext(packageElement);

        if (newPackage.isNeedsRefresh()) {

            return newPackage;

        }

        if (LOG.isDebugEnabled()) {

            LOG.debug("Loaded " + newPackage);

        }

        // add result types (and default result) to this package

        addResultTypes(newPackage, packageElement);

        // load the interceptors and interceptor stacks for this package

        loadInterceptors(newPackage, packageElement);

        // load the default interceptor reference for this package

        loadDefaultInterceptorRef(newPackage, packageElement);

        // load the default class ref for this package

        loadDefaultClassRef(newPackage, packageElement);

        // load the global result list for this package

        loadGlobalResults(newPackage, packageElement);

        // load the global exception handler list for this package

        loadGlobalExceptionMappings(newPackage, packageElement);

        // get actions

        NodeList actionList = packageElement.getElementsByTagName("action");

        for (int i = 0; i < actionList.getLength(); i++) {

            Element actionElement = (Element) actionList.item(i);

            addAction(actionElement, newPackage);

        }

        // load the default action reference for this package

        loadDefaultActionRef(newPackage, packageElement);

        configuration.addPackageConfig(newPackage.getName(), newPackage);

        return newPackage;

    }

   

    活動圖如圖19所示:

 

(圖19

    配置信息加載完成後,建立一個Action的代理對象——ActionProxy引用,實際上對Action的調用正是經過ActionProxy實現的,而ActionProxy又由ActionProxyFactory建立,ActionProxyFactory是建立ActionProxy的工廠。

注:ActionProxyActionProxyFactory都是接口,他們的默認實現類分別是DefaultActionProxyDefaultActionProxyFactory,位於com.opensymphony.xwork2包下。

    在這裏,咱們絕對有必要介紹一下com.opensymphony.xwork2.DefaultActionInvocation類,該類是對ActionInvocation接口的默認實現,負責Action和截攔器的執行。

    DefaultActionInvocation類中,定義了invoke()方法,該方法實現了截攔器的遞歸調用和執行Actionexecute()方法。其中,遞歸調用截攔器的代碼如清單17所示:

代碼清單17:調用截攔器,DefaultActionInvocation.invoke()方法的部分代碼

       if (interceptors.hasNext()) {

              //從截攔器集合中取出當前的截攔器

               final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();

               UtilTimerStack.profile("interceptor: "+interceptor.getName(),

                      new UtilTimerStack.ProfilingBlock<String>() {

                         public String doProfiling() throws Exception {

                            //執行截攔器(Interceptor)接口中定義的intercept方法

                             resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);

                            returnnull;

                         }

               });

           }

    從代碼中彷佛看不到截攔器的遞歸調用,實際上是否遞歸徹底取決於程序員對程序的控制,先來看一下Interceptor接口的定義:

代碼清單18Interceptor.java

publicinterface Interceptor extends Serializable {

    void destroy();

    void init();

    String intercept(ActionInvocation invocation) throws Exception;

}

    全部的截攔器必須實現intercept方法,而該方法的參數偏偏又是ActionInvocation,因此,若是在intercept方法中調用invocation.invoke(),代碼清單17會再次執行,從ActionIntercepor列表中找到下一個截攔器,依此遞歸。下面是一個自定義截攔器示例:

代碼清單19CustomIntercepter.java

publicclass CustomIntercepter extends AbstractInterceptor {

    @Override

    public String intercept(ActionInvocation actionInvocation) throws Exception

    {

       actionInvocation.invoke();

       return"李贊紅";

    }

}

    截攔器的調用活動圖如圖20所示:

 

(圖20

    若是截攔器所有執行完畢,則調用invokeActionOnly()方法執行ActioninvokeActionOnly()方法基本沒作什麼工做,只調用了invokeAction()方法。

    爲了執行Action,必須先建立該對象,該工做在DefaultActionInvocation的構造方法中調用init()方法早早完成。調用過程是:DefaultActionInvocation()->init()->createAction()。建立Action的代碼以下:

代碼清單20DefaultActionInvocation.createAction()方法

    protectedvoid createAction(Map contextMap) {

        try {

            action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);

        } catch (InstantiationException e) {

       ……異常代碼省略

        }

    }

    Action建立好後,輪到invokeAction()大顯身手了,該方法比較長,但關鍵語句實在不多,用心點看不會很難。

代碼清單20DefaultActionInvocation.invokeAction()方法

protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {

    //獲取Action中定義的execute()方法名稱,實際上該方法是能夠隨便定義的

        String methodName = proxy.getMethod();

        String timerKey = "invokeAction: "+proxy.getActionName();

        try {

            UtilTimerStack.push(timerKey);           

            Method method;

            try {

              //將方法名轉化成Method對象

                method = getAction().getClass().getMethod(methodName, new Class[0]);

            } catch (NoSuchMethodException e) {

                // hmm -- OK, try doXxx instead

                try {

                  //若是Method出錯,則嘗試在方法名前加do,再轉成Method對象

                    String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);

                    method = getAction().getClass().getMethod(altMethodName, new Class[0]);

                } catch (NoSuchMethodException e1) {

                    // throw the original one

                    throw e;

                }

            }

           //執行方法

            Object methodResult = method.invoke(action, new Object[0]);

            //處理跳轉

        if (methodResult instanceof Result) {

                this.result = (Result) methodResult;

                returnnull;

            } else {

                return (String) methodResult;

            }

        } catch (NoSuchMethodException e) {

              ……省略異常代碼

        } finally {

            UtilTimerStack.pop(timerKey);

        }

    }

    剛纔使用了一段插述,咱們繼續回到ActionProxy類。

    咱們說Action的調用是經過ActionProxy實現的,其實就是調用了ActionProxy.execute()方法,而該方法又調用了ActionInvocation.invoke()方法。歸根到底,最後調用的是DefaultActionInvocation.invokeAction()方法。

    如下是調用關係圖:

    

    其中:

Ø         ActionProxy:管理Action的生命週期,它是設置和執行Action的起始點。

Ø         ActionInvocation:在ActionProxy層之下,它表示了Action的執行狀態。它持有Action實例和全部的Interceptor

    如下是serviceAction()方法的定義:

代碼清單21Dispatcher.serviceAction()方法

        publicvoid serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,

                              ActionMapping mapping) throws ServletException {

        Map<String, Object> extraContext = createContextMap(request, response, mapping, context);

        // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action

        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

        if (stack != null) {

            extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));

        }

        String timerKey = "Handling request from Dispatcher";

        try {

            UtilTimerStack.push(timerKey);

            String namespace = mapping.getNamespace();

            String name = mapping.getName();

            String method = mapping.getMethod();

            Configuration config = configurationManager.getConfiguration();

            ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(

                    namespace, name, extraContext, truefalse);

            proxy.setMethod(method);

            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

            // if the ActionMapping says to go straight to a result, do it!

            if (mapping.getResult() != null) {

                Result result = mapping.getResult();

                result.execute(proxy.getInvocation());

            } else {

                proxy.execute();

            }

            // If there was a previous value stack then set it back onto the request

            if (stack != null) {

                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);

            }

        } catch (ConfigurationException e) {

            LOG.error("Could not find action or result", e);

            sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);

        } catch (Exception e) {

            thrownew ServletException(e);

        } finally {

            UtilTimerStack.pop(timerKey);

        }

    }

    最後,經過Result完成頁面的跳轉。

3.4 本小節總結

       整體來說,Struts2的工做機制比Struts1.x要複雜不少,但咱們不得不佩服Struts和WebWork開發小組的功底,代碼如此優雅,甚至可以感覺看到兩個開發小組心神相通的默契。兩個字:佩服。

       如下是Struts2運行時調用方法的順序圖:

 

(圖21)

4、      總結

閱讀源代碼是一件很是辛苦的事,對讀者自己的要求也很高,一方面要有紮實的功底,另外一方面要有超強的耐力和恆心。本章目的就是但願能幫助讀者理清一條思路,在必要的地方做出簡單的解釋,達到事半功倍的效果。

固然,筆者不可能爲讀者解釋全部類,這也不是個人初衷。Struts2+xwork一共有700餘類,除了爲讀者作到如今的這些,已沒法再作更多的事情。讀者能夠到Struts官方網站下載幫助文檔,慢慢閱讀和理解,相信會受益頗豐。

本章並不適合java語言初學者或者對java博大精深的思想理解不深的讀者閱讀,這其中涉及到太多的術語和類的使用,特別不要去鑽牛角尖,容易使自信心受損。基本搞清楚Struts2的使用以後,再回過頭來閱讀本章,對一些知識點和思想也許會有更深的體會。

若是讀者的java功底比較渾厚,並且對Struts2充滿興趣,但又沒太多時間研究,不妨仔細閱讀本章,再對照Struts的源代碼,但願對您有所幫助。

相關文章
相關標籤/搜索