迷失
          我真的迷失了,迷失了自己,哪里是屬于我的地方?哪里有我的愛?
          posts - 7,comments - 12,trackbacks - 0

          Written by 王海龍 buaawhl@sina.com

          0.簡介

          本文介紹Java Web Framework的基本工作原理,和一些常用的開源Web MVC Framework(Struts, Web Work, Tapestry, Echo, JSF, Maverick, Spring MVC, Turbine, Cocoon, Barracuda)

          Web開發的最重要的基本功是HTTPJava Web開發的最重要的基本功是Servlet SpecificationHTTPServlet Specification對于Web ServerWeb Framework的開發實現來說,是至關重要的協議規范。

          應用和剖析開源Web Framework,既有助于深入掌握HTTP & Servlet Specification, 也有助于了解一些現代的B/S Web框架設計思想,如MVC,事件處理機制,頁面組件,IoCAOP等。在這個現代化的大潮中,即使Servlet規范本身也不能免俗,不斷引入FilterListener等現代框架設計模式。同是Sun公司出品的JSF更是如此。

          關于MVC模型、項目簡介、配置文件、入門示例等基礎知識,網上已經有大量的重復資料信息,本文不再贅述。

          文中會提到一些相關的開源項目,和一些編程思想,如有需要,可以用相關的關鍵字在網上搜索,獲取基本的背景知識。

          本文力圖言簡意賅,突出重點。著重描述其他資料沒有提到、或很少提到的較重要內容,如運行原理、主流用法,相關知識,關鍵特性等。

          1. Java Web程序工作原理

          [編者按:本部分內容在本期雜志Servlet規范簡介》有更詳細介紹]

          TomcatServer.xml文件中定義了網絡請求路徑到主機本地文件路徑的映射。比如,<context path="/yourapp" docBase="yourapp_dir/webapp"/>

           

          我們來看一下,一個HTTP Request-Response Cycle的處理過程。

          HTTP Request URL一般分為三段:host, context, path

          http://yourhost/yourapp/en/index.html這個URL,分為host=yourhost, context=yourapp, path=en/index.html三段。其中,Context部分由request.getContext()獲得,path部分由request.getServletPath()獲得(返回結果是“/en/index.html”)。

          yourhost主機上運行的Tomcat Web Server接收到這個URL,根據Context定義,把yourapp這個網絡路徑映射為yourapp_dir/webapp,并在此目錄下定位en/index.html這個文件,返回到客戶端。

           

          如果我們這個URL更換為http://yourhost/yourapp/en/index.jsp,這個時候Tomcat會試圖把yourapp_dir/webapp/en/index.jsp文件編譯成Servlet,并調用運行這個Servlet

          我們再把這個URL更換為http://yourhost/yourapp/en/index.do

          注意,戲劇化的事情就發生在這個時候,Servlet規范中最重要的類RequestDispatcher登場了。RequestDispatcher根據WEB-INF/web.xml配置文件的定義,調用對應的Servlet來處理en/index.do這個路徑。

          假設web.xml里面有這樣的定義。

            <servlet>

              <servlet-name>DispatchServlet</servlet-name>

              <servlet-class>yourapp.DispatchServlet</servlet-class>

            </servlet>

            <servlet-mapping>

              <servlet-name>DispatchServlet</servlet-name>

              <url-pattern>*.do</url-pattern>

            </servlet-mapping>

          那么,RequestDispatcher會調用yourapp.DispatchServlet類處理這個路徑。

          如果web.xml沒有定義對應en/index.do這個路徑的Servlet,那么Tomcat返回“您請求的資源不存在”。

          RequestDispatcher用于Web Server中,也可以用于應用程序中進行處理轉向,資源定位。比如,我們在處理en/index.do的代碼中調用,

          request.getRequestDispatcher(“cn/index.jsp”).forward(request, response), 就可以轉交另外的資源cn/index.jsp來處理。

           

          幾乎所有的Web Framework都需要定義自己的Dispatch作用的Servlet,并調用RequestDispatcher進行轉向處理。

          閱讀Web Framework源代碼,有兩條主要線索,(1)根據web.xml找到對應的Servlet類;(2)搜索包含“RequestDispatcher”詞的代碼文件。

           

          我們看到,request, response   這兩個參數,被RequestDispatcher在各種Servlet之間傳來傳去(JSP也是Servlet)。所以,requestsetAttribute()getAttribute()方法是Servlet之間傳送數據的主要方式。

          MVC結構中,一般的處理流程如下:

          處理HTTP Request的基本單位一般稱為Action,是一個比Servlet輕量得多的接口定義,通常只有一兩個方法,如execute(perform), validate等。

          我們知道,URL->Servlet映射,定義在Web.xml配置文件里,但MVC框架通常會有另外一個定義URL-> Action映射的配置文件。

          入口Dispatcher Servlet根據URL -> Action的映射關系,把請求轉發給Action

          Action獲得輸入參數,調用商業邏輯,并把結果數據和View標識給(Model & View)返回給Dispatcher Servlet

          Dispatcher Servlet根據這個View 標識,定位相應的View Template Path,把處理轉交給ViewJSP +TagLib, Velocity, Free Marker, XSL等)。

          View一般通過request.getAttribute()獲得結果數據,并顯示到客戶端。至于是誰把結果數據設置到request.attribute里面,有兩種可能:ActionDispatcher Servlet

          2. Struts

          http://struts.apache.org/

          Struts是目前用戶群最大、開發廠商支持最多的開源Web Framework

          Struts勞苦功高,為普及MVC框架作出了不可磨滅的貢獻。顯赫的聲望,趨于老化的厚重結構,令Struts成為很多現代Web Framework參照、挑戰的目標。

           

          Struts應用主要包括3件事情: 配置struts-config.xml文件,實現Action類,實現View;還有一些高級擴展用法。下面分別講述。

           

          1. 配置struts-config.xml文件:

          Struts支持多級配置文件,具體用法和限制,詳見Struts文檔。這里只討論struts-config.xml主流配置的內容。:-)

           

          (1) URL PathAction的映射。

          <action path="/LogonSubmit" type="app.LogonAction" ... />

           

          Struts的入口ServletActionServlet

          ActionServlet需要此信息把URL Path調用對應的Action類處理。

          Struts運行期間,一個URL Path,只存在一個對應的Struts Action實例。所有的該URL Path的請求,都經過這同一個Struts Action實例處理。所以Struts Action必須線程安全。

          想想看,其實這個要求并不過分,Action只是一個處理程序,不應該保存跨HTTP請求的狀態數據,按理來說,也應該做成線程安全的。

           

          (2) Template NameView Template Path的映射。

          <forward name="success" path="/pages/Welcome.jsp"/>

           

          Action類返回一個Template NameActionServlet根據這個Template Name獲得對應的View Template Path,然后調用

          request.getRequestDispatcher(“View Template Path”),把處理轉向路徑對應的Servlet。在這個例子中,是轉向/pages/Welcome.jsp編譯后的Servlet

           

          我們來看一個一個Velocity的例子。

          <include name="success" path="/pages/Welcome.vm"/>

          web.xml的定義如下

          <servlet>

            <servlet-name>velocity</servlet-name>

          <servlet-class>org.apache.velocity.tools.view.servlet.VelocityViewServlet</servlet-class>

          </servlet>

          <servlet-mapping>

            <servlet-name>velocity</servlet-name>

            <url-pattern>*.vm</url-pattern>

          </servlet-mapping>

           

          這時,request.getRequestDispatcher(“/pages/Welcome.vm”)會調用VelocityViewServlet,由VelocityViewServlet負責裝并驅動運行/pages/Welcome.vm這個模板文件。

          這里面有一個問題,如果調用的是DispatchRequester.include()方法,那么如何才能把pages/Welcome.vm傳給VelocityViewServlet呢?

          如前所說,RequestDispatcher傳遞的參數只有兩個,requestresponse。那么只能通過request attribute。正是為了解決這個問題,Servlet2.3規范之后,加入了javax.servlet.include.servlet_path這個屬性。

          參見VelocityViewServlet的代碼(velocity-tool開源項目)

          // If we get here from RequestDispatcher.include(), getServletPath()

          // will return the original (wrong) URI requested.  The following special

          // attribute holds the correct path.  See section 8.3 of the Servlet

          // 2.3 specification.

          String path = (String)request.getAttribute("javax.servlet.include.servlet_path");

           

          從這里我們可以看出,為什么通曉Servlet Specification對于通曉Web Framework至關重要。

           

          (3) Form Bean的定義

          <form-bean name="logonForm" type="app.LogonForm"/>
          Struts Form Bean需要繼承ActionForm類。

          Form Bean類,主要有三個作用:

          [1]根據bean的定義,利用reflection機制,自動把request參數轉化為需要的數據類型,填入到bean的屬性當中。ActionForm類名中雖然有Form這個詞,但不僅能夠獲取Form提交后的HTTP Post參數,也可以獲取URL后綴的HTTP Get參數。

          [2]輸入驗證。用戶可以配置validation.xml,定義各屬性的驗證規則。

          [3]當作View Object來用。用戶需要熟練掌握Struts HTML TagLib的用法,才能把Form Bean的屬性正確顯示出來。

           

          (4)其他定義。詳見Struts文檔。不再贅述。

           

          2.實現Action

          Action類從Form Bean或直接從request中獲得輸入參數,調用商業邏輯,把結果數據(也許會包裝成View Object),用request.setAttribute()放到request中,最后返回一個用ForwardMapping類包裝的Template Name

           

          3.實現View

          Struts View的標準實現方法是JSP + Struts TagLib,其中最重要的就是Struts HTML TagLib

          html:form tag則是整個HTML Tag的核心,其它的如html:input, html:selecttag,都包含在html:form tag里面。

          html:form tag用來映射Form Bean(也可以通過適當定義,映射其他的bean,但使用上會有很多麻煩)。html:form tag包含的其他Struts html tag用來映射Form Bean的屬性。

           

          Struts Bean TagLib的用法比較臃腫,一般情況下可以用JSTL代替。當然,如果需要用到bean:message tag實現國際化,那又另當別論。

          Struts Tile TagLib用于頁面布局。開源Portal項目Liferay使用了Struts Tile TagLib做為布局控制。

           

          4.高級擴展用法

          用戶可以重載Struts的一些控制類,引入自己的一些定制類。詳見Struts文檔。

          本文不是Struts專題,只講述最重要的主流用法,其它邊邊角角的,不再贅述。

          3. WebWork

          http://www.opensymphony.com/webwork/

          WebWork由于靈活的可插拔特性,受到很多資深程序員的歡迎。似乎很有可能大肆流行起來。

          WebWork項目建立在XWork項目上。入口ServletWebWork項目中定義的ServletDispatcher,而ActionXWork項目中定義。

          XWork Action接口的execute()方法沒有參數,不像Struts Action那樣接受request, response參數,所以XWork Action能夠脫離Web環境被直接調用,便于單元測試。

          這里引入了一個問題。沒有了request參數,那么XWork Action如何獲得request parameters作為輸入數據?又通過什么橋梁(Strutsrequest.setAttribute)把結果數據傳送到View層?

          Web Work中,只能通過Action本身的getter, setter屬性來傳送輸入參數和輸出結果。

          比如,我們有這樣一個實現了XWork Action接口的類,

          YourAction implements Action{

            int productId = null;

            String productName = null;

           

            public void setProductId(int productId){this.productId = productId;}

            public String getProductName(){return productName;}

           

            public String execute(){

                productName = findNameById(productId);

                return “success”;

            }

          }

          這個類里面的productId將接受request輸入參數,productName是輸出到頁面顯示的結果。

          比如,這樣的請求,http://yourhost/yourapp/MyAction.action?productId=1

          Web Work會把1填到YourActionproductId里面,然后執行execute()方法,JSP里的語句<ww:property value=“productName”>會把YourActionproductName顯示在頁面上。

           

          如果一個Web Framework采用了這種屏蔽Actionrequest, response參數的設計方式,一般也同時會采用這種Action和輸入輸出數據結合成一體的解決方式。類似的情形也存在于TapestryMaverick中,后面會講到。

          WebWork ServletDispatcher接收到HTTP Request的時候,首先把所有相關的信息(包括request, response, session, servlet config, servelt context, 所有request參數)等存放到AcationContext中,然后根據Interceptor配置信息,生成一個YourAction的動態代理類對象。實際上運行的正是這個代理對象,如同Servlet Filter的工作機制一般,所有注入的Interceptor方法會先于Actio方法運行。

          我們來看一下ActionInterceptor的地位:Action沒有參數,無法獲得ActionContext;而Interceptor接受的ActionInvoication參數擁有包括ActionContext在內的所有重要信息。

          這種權力分配的不平等,注定了Action的作用非常有限,只限于調用商業邏輯,然后返回一個成功與否標志。所有與外部Web世界打交道、協調內部工作流程的重擔,都責無旁貸地落在Interceptor的肩上。

          我們可以設想一個極端的例子。我們聲明一批不做任何事情的空Action,我們只是需要它們的空殼類名;我們制作一批對應的Interceptor,所有的轉發控制、商業邏輯都在Interceptor上實現,然后把Interceptor都注入到對應的空Action。這在理論上是完全可行的。

          Web海洋的包圍中,Action可少,Interceptor不可少。Action是一個孤島,如果沒有外來盟友Interceptor的協助,只能在自己的小范圍內獨立作戰(比如Unit Test),而對整體大局的作戰目標無法產生影響。

          下面我們來看一下Action是如何在Interceptor的全程監管下工作的。

           

          WebWork中,我們需要如下配置XWork.xml

          <xwork>

          <!-- Include webwork defaults (from WebWork-2.1 JAR). -->

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

           

          <!-- Configuration for the default package. -->

          <package name="default" extends="webwork-default">

              <!-- Default interceptor stack. -->

              <default-interceptor-ref name=" defaultStack" />

           

              <!-- Action: YourAction. -->

              <action name="youraction" class="yourapp.YourAction">

                 <result name="success" type="dispatcher">

          YourAction.jsp

          </result>

          </action>

          </package>

          </xwork>

           

          webwork-default.xml里面的相關定義如下:

          <interceptors>

          <interceptor name="validation" class="com.opensymphony.xwork.validator.ValidationInterceptor"/>

           

          <interceptor name="static-params" class="com.opensymphony.xwork.interceptor.

          StaticParametersInterceptor"/>

          <interceptor name="params" class="com.opensymphony.xwork.interceptor.ParametersInterceptor

          "/>

          <interceptor name="conversionError" class="com.opensymphony.webwork.interceptor.

          WebWorkConversionErrorInterceptor"/>

          <interceptor-stack name="defaultStack">

              <interceptor-ref name="static-params"/>

              <interceptor-ref name="params"/>

              <interceptor-ref name="conversionError"/>

          </interceptor-stack>

          </interceptors>

           

          從上述的配置信息中可以看出,YourAction執行execute()方法的前后,會被

          defaultStack所定義的三個Intercepter截獲。這些Interceptor的任務之一就是把輸入參數設置到Action的對應屬性當中。

          如果我們需要加入對YourAction的屬性的驗證功能,只要把上述定義中的validation Interceptor加入到defaultStack中就可以了。當然,實際工作還沒有這么簡單,一般來說,還要為每個進行屬性驗證的Action的都配置一份validation.xml

          XWork Interceptor能夠在PackageAction級別上,進行截獲處理。

          Servlet Filter能夠在URL Patten級別上,進行截獲處理。雖然實際上,Servlet Filter截獲的是Servlet,但某些情況下,可以達到和截獲一批Action的同樣效果。

          比如,在Web Work中,我們可以為所有admin packageAction,加入一個Interceptor,當檢查到當前Session的用戶沒有admin權限時,統一返回一個警告頁面:您沒有足夠的權限執行這個操作。

          我們看到也可以為所有URL Pattern為“admin/*.action”的URL定義一個Servlet Filter,當檢查到當前Session的用戶沒有admin權限時,統一返回一個警告頁面:您沒有足夠的權限執行這個操作。

           

          WebWorkInterceptor配置是相當靈活的,相當于對Action實現了AOPInterceptor相當于Aspect,基類AroundInterceptorbefore(), after()方法相當于Advice

          另外,XWork也提供了從XML配置文件裝配Component的機制,相當于實現了對于ComponentIoC

          提到AOPIoC,順便多講兩句。Spring AOP能夠截獲所有Interface,不限于某個特定接口;Spring框架支持所有類型的IoC,不限于某種特定類型。

           

          要知道,AOP, IoC可是現在最時髦的東西,一定不要錯過啊。:D

          相關概念導讀(如果需要,請用如下關鍵字搜索網絡):

          AOP -- Aspect Oriented Programming -- 面向方面編程。

          IoC – Inversion of Control --控制反轉

          Dynamic Proxy -- 動態代理,JDK1.4引入的特性。還可以進一步參考CGLib, ASM等開源項目。

           

          WebWork直接支持所有主流View -- XSL,Velocity, FreeMarker,JSPWebWork還提供了自己的TagLib。“直接支持”的意思是說,不用像Struts那樣,使用Velocity的時候,還需要引入輔助橋梁Velocity-tool

          WebWork中用到一種功能和XPath類似的對象尋徑語言ONGL,是一個開源項目。ONGL同樣用在下面要介紹的Tapestry項目中。

          Opensymphony下還有一個SiteMesh項目,通過Servlet Filter機制控制布局。可以和WebWork組合使用。

           

          4. Tapestry

          http://jakarta.apache.org/tapestry/

          Tapestry近來突然火了起來,令我感到吃驚。也許是JSF帶來的Page Component風潮令人們開始關注和追逐Tapestry

          Tapestry的重要思想之一就是Page Component

          前面講到,XWork能夠自動把request參數映射到Action的屬性當中。Tapestry走得更遠,甚至能夠根據request參數,映射到ActionTapestry里面稱為Page)的方法,并把request參數映射為Page方法需要的參數,進行正確的調用。就這樣,Tapestry不僅把輸入輸出數據,而且把事件方法也綁定到了Page上面。

          Tapestry框架中,Action的概念已經非常模糊,而換成了Page的概念。而Tapestry Page是擁有屬性和事件的頁面組件,其中的事件處理部相當于Action的職責,而屬性部分起著Model的作用。

          除了使用Page和其它的Tapestry頁面組件,用戶也可以自定義頁面組件。

           

          這種頁面組件/屬性事件的編程模型,受到一些程序員的歡迎。當然,這種編程模型并不是沒有代價的,每個Tapestry模板文件都需要一個對應的.page文件。這些.page文件定義了頁面組件的屬性、事件、Validator等信息。

           

          我們來看一下B/S結構中,組件的屬性、事件和HTTP Request綁定的基本原理。一個能夠發出請求的頁面組件(比如LinkButton),在輸出自己的HTML的時候,需要輸出一些特殊的信息來標志本組件的屬性/事件,這樣下次HTTP Request來的時候,會把這些信息帶回來,以便Web Framework加以辨認識別,發給正確的Page Component處理。

          這些特殊信息通常包含在URL參數或Hidden Input里面,必要的時候,還需要生成一些Java ScriptTapestryEchoJSF都是這種原理。

          Tapestry的例子如下:

          <a href="#" jwcid="@DirectLink" parameters="ognl:currentItem.itemId" listener="ognl:listeners.showItem">

          [編者按:OGNL是一種利用java對象settergetter方法來訪問其屬性的表達式語言,Tepestry項目及很多項目使用了該技術。更詳細鏈接http://www.ognl.org/]

          JSFTagLib實現頁面組件,也提供了類似的CommandLinkCommandButton Tag。其中對應Tapestry listenerTag屬性是action。后面會講解。

           

          Tapestry的模板標簽是HTML標簽的擴展,具有良好的“所見即所得”特性,能夠直接在瀏覽器中正確顯示,這也是Tapestry的一個亮點。

          5. Echo

          http://sourceforge.net/projects/echo

          Echo提供了一套類似Swing的頁面組件,直接生成HTML

          從程序員的角度看來,用Echo編寫Web程序,和用Swing編寫Applet一樣,屬于純面向組件事件編程,編程模型也以Event/Listener結構為主體。

          Echo沒有Dispatcher Servlet,也沒有定義URL->Action映射的配置文件。

          EchoAction就是實現了ActionListener接口(參數為ActionEvent)的Servlet(繼承EchoServer類)。

          所以,Echo直接由Web Server根據web.xml配置的URL -> Servlet的映射,進行轉發控制。

           

          Echo也沒有明顯的View層,Echo在頁面組件方面走得更遠,所有的HTMLJavaScript都由框架生成。你不必(也沒有辦法)寫HTML,只需要(也只能)在Java代碼中按照類似Swing編程方式,生成或操作用戶界面。用戶也可以定制自己的Echo組件。

          EchoUI Component的實現,采用了兩個重要的模式。一個是PeerComponent -> ComponentPeer)模式,一個是UI Component -> Renderer模式。

          雖然EchoAPI更類似于Swing,但實現上卻采用更接近于AWTPeer模式。每個Component類(代表抽象的組件,比如Button),都有一個對應的ComponentPeer類(代表實際的組件,比如windows桌面的ButtonLinux桌面的ButtonHTML Button等)。

          先別急,這個事情還沒有完。雖然ComponentPeer落實到了具體的界面控件,但是它還是舍不得顯示自己,進一步把顯示工作交給一個Renderer來執行。

          比如,在Echo里面,Button類對應一個ButtonUI(繼承了ComponentPeer)類,而這個ButtonUI類會把最終顯示交給ButtonRender來處理。

          據說多了這么一步,能夠讓顯示控制更加靈活豐富。比如,同一個Renderer可以處理不同的UI Component,同一個UI Component也可以交給不同的Renderer處理。

          JSF的頁面組件也采用了UI Component -> Renderer模式,后面會講到。

          6. JSF

          http://java.sun.com/j2ee/javaserverfaces/index.jsp

          http://wwws.sun.com/software/communitysource/jsf/download.html download source

           

          JSF的中心思想也是頁面組件/屬性事件。一般來說,JSF的頁面組件是一個三件套{ UI Component, Tag, Renderer}

          UI Component有可能對應ModelEventListenerTag包含componentTyperendererType兩個屬性,用來選擇對應的的UI ComponentRenderer

          JSF的應用核心無疑是JSF TagLibJSF TagLib包含了對應所有重要HTML元素的Tag,而且Input Tag可以直接包含Validator Tag或者Validator屬性,來定義驗證手段。

           

          我們通過JSF攜帶的cardemo例子,來看JSF的處理流程。

          (1) carDetail.jsp有如下內容:

          <h:commandButton action="#{carstore.buyCurrentCar}" value="#{bundle.buy}" />

          可以看到,這個buttonsubmit actioncarstore.buyCurrentCar方法綁定在一起。我們在Tapestry里面曾經看到過類似的情景。

           

          (2) carstorefaces-config.cml中定義:

            <managed-bean>

              <managed-bean-name> carstore </managed-bean-name>

              <managed-bean-class> carstore.CarStore </managed-bean-class>

              <managed-bean-scope> session </managed-bean-scope>

            </managed-bean>

           

          (3) carstore.CarStore類中的buyCurrentCar方法如下:

              public String buyCurrentCar() {

                  getCurrentModel().getCurrentPrice();

                  return "confirmChoices";

              }

           

          (4) confirmChoices轉向在faces-config.cml中定義:

            <navigation-rule>

              <from-view-id>/carDetail.jsp</from-view-id>

              <navigation-case>

                <description>

                  Any action that returns "confirmChoices" on carDetail.jsp should

                  cause navigation to confirmChoices.jsp

                </description>

                <from-outcome>confirmChoices</from-outcome>

                <to-view-id>/confirmChoices.jsp</to-view-id>

              </navigation-case>

            </navigation-rule>

           

          (5)于是轉到頁面confirmChoices.jsp

           

          除了Interceptor之外,JSF幾乎包含了現代Web Framework應該具備的所有特性:頁面組件,屬性事件,IoC (ManagedBean)Component -> Renderer,類似于Swing ComponentModel-Event-Listener

          也許設計者認為,眾多龐雜的模式能夠保證JSF成為一個成功的框架。Portal開源項目eXo就是建立在JSF框架上。

           

          可以看出這樣一個趨勢,現代Web Framework認為B/S結構的無狀態特性和HTML界面是對編程來說是需要極力掩蓋的一個缺陷,所以盡量模擬C/S結構的組件和事件機制,以吸引更多的程序員。

          7. Maverick

          http://mav.sourceforge.net/

          Maverick是一個輕量而完備的MVC Model 2框架。MaverickAction不叫Action,直截了當的稱作Controller

          Controller只接受一個ControllerContext參數。requestresponse, servlet config, servelt context等輸入信息都包裝在ControllerContext里面,而且Model也通過ControllerContextmodel屬性返回。整個編程結構清晰而明快,令人贊賞。

          但這個世界上難有十全十美的事情,由于ControllerContext只有一個model屬性可以傳遞數據,程序員必須把所有需要的數據都打包在一個對象里面設置到model屬性里。這種麻煩自然而然會導致這樣的可能用法,直接把Controller本身設置為model,這又回到了Controller(Action)Model一體的老路。

           

          前面講到,WebWork也把所有的輸入信息都包裝在ActionContext里面,但Action并沒有權力獲取。而在Maverick中,Controller對于ControllerContext擁有全權的控制,兩者地位不可同日而語。當然,由于參數ControllerContext包含requestreponse之類信息,這也意味著,Maverick Controller不能像WebWork Action那樣脫離Web環境獨立運行。

          當然,這也并不意味著任何結構性缺陷。程序的結構由你自己控制,你完全可以把需要Unit Test的那部分從Web環境脫離開來,放到Business層。

          如同WebWorkMaverick直接支持所有的主流ViewMaverick的配置文件采Struts, Cocoon兩家之長,URL -> Action -> View映射的主體結構類似于Struts,而View定義部分對Transform的支持則類似于Cocoon。如:

          <command name="friends">

          <controller class="org.infohazard.friendbook.ctl.Friends"/>

          <view name="success" path="friends.jsp">

                 <transform path="trimInside.jsp"/>

          </view>

          </command>

          8. Spring MVC

          http://www.springframework.com/

          Spring MVC是我見過的結構最清晰的MVC Model 2實現。

          Action不叫Action,準確地稱做ControllerController接收request, response參數,干脆利落地返回ModelAndView(其中的Model不是Object類型,而是Map類型)。

          其它的Web Framework中, Action返回值一般都只是一個View NameModel則需要通過其它的途徑(如request.attributeContext參數,或Action本身的屬性數據)傳遞上去。

           

          Spring以一招IoC名滿天下,其AOP也方興未艾。“Spring出品,必屬精品”的觀念已經深入人心。我這里多說也無益,強烈建議讀者去閱讀Spring Doc & Sample & Code本身。

          9. Turbine

          http://jakarta.apache.org/turbine/

          Turbine是一個提供了完善權限控制的堅實框架(Fulcrum子項目是其基石)。Turbine的個人用戶不多,但不少公司用戶選擇Turbine作為框架,開發一些嚴肅的應用(我并沒有說,用其它框架開發的應用就不嚴肅^_^)。Portal開源項目JetSpeed建立在Turbine上。

          TurbineRunData來傳遞輸入輸出數據。如同MaverickControllerContextRunData是整個Turbine框架的數據交換中心。除了request, response等基本信息,RunData直接包括了User/ACL等權限控制相關的屬性和方法,另外還包括Action NameTarget Template Name等定位屬性。

          ModuleTurbine里面除了RunData之外的又一個核心類,是Turbine框架的基本構件,ActionModuleScreen也是ModuleTurbine提供了LoginUserLogoutUser兩個Action作為整個系統的出入口。而其余流量的權限控制則由類似于Servlet Filter機制的Pipeline控制。

          Turbine Pipeline的編程模型和Servlet Filter一模一樣:Turbine PipelineValve就相當于Servlet Filter,而ValveContext則相當于Filter Chain。還有更相近的例子,Tomcat源代碼里面也有ValveValueContext兩個類,不僅編程模型一樣,而且名字也一樣。

           

          權限控制貫穿于Turbine框架的始終。要用好Turbine,首先要通曉子項目Fulcrum Security部分的權限實現模型。

          Fulcrum Security的權限實體包括四個-- User, Group, Role, Permission

          實體之間包含{RolePermission}{ Group, User, Role}兩組關系。

          {RolePermission}是多對多的關系,一個Role可以具有各種Permission{ Group, User, Role}之間是多對多的關系,一個Group可包含多個User,并可以給User分配不同的Role

          權限模型的實現同樣采用Peer模式,Entity -> EntityPeer, Entity -> ManagerPeer

          EntityEntityManger代表抽象的模型概念,而EntityPeerManagerPeer代表具體的實現。

          用戶可以根據模型,提供不同的實現,比如,用內存結構中實現,用數據表結構實現,與Windows NT權限驗證機制結合,與OSWorkflow的權限控制模型結合,等等。其中,用數據表結構實現,又可以選擇用Torque實現,或者用Hibernate實現。(TorqueTurbineO/R Mapping子項目)

           

          例如,Falcrum.property配置文件包含如下Security相關選項:

          # -------------------------------------------------------------------

          #  S E C U R I T Y  S E R V I C E

          # -------------------------------------------------------------------

          services.SecurityService.user.class=org.apache.fulcrum.security.impl.db.entity.TurbineUser

          services.SecurityService.user.manager=org.apache.fulcrum.security.impl.db.DBUserManager

          services.SecurityService.secure.passwords.algorithm=SHA

          # -------------------------------------------------------------------

          #  D A T A B A S E  S E R V I C E

          # -------------------------------------------------------------------

          services.DatabaseService.database.newapp.driver=org.gjt.mm.mysql.Driver

          services.DatabaseService.database.newapp.url=jdbc:mysql://127.0.0.1/newapp

          services.DatabaseService.database.newapp.username=turbine

          services.DatabaseService.database.newapp.password=turbine

           

          這說明,權限控制實現由數據庫提供,需要根據權限模型創建如下數據表:

          TURBINE_USERTURBINE_ROLETURBINE_GROUP

          TURBINE_PERMISSIONTURBINE_ROLE_PERMISSION

          TURBINE_USER_GROUP_ROLE

           

          10. Cocoon

          http://cocoon.apache.org

          Cocoon項目是一個叫好不叫做的框架。采用XML + XSLT Pipeline機制,Java程序只需要輸出XML數據,Cocoon框架調用XSL文件把XML數據轉換成HTMLWML等文件。

          Cocoon強大靈活的XSL Pipeline配置功能,XSLT的內容/顯示分離的承諾,一直吸引了不少程序員fans。怎奈天不從人愿,由于復雜度、速度瓶頸、XSL學習難度等問題的限制,Cocoon一直主要限于網站發布出版領域,向CMSPortal方向不斷發展。另外,Cocoon開發了XSP腳本和Cocoon Form技術。

          Cocoonsitemap.xmap配置文件比較復雜,與其它的Web Framework差別很大。

          主體Pipelines配置部分采用Pattern Match的方式,很像XSL語法,也可以類比于Web.xml里面Servlet Mapping的定義。比如,一個典型的URL->Action的映射定義看起來是這個樣子:

          <map:pipelines>

          <map:pipeline>

          <map:match pattern="*-dept.html">

            <map:act set="process">

              <map:parameter name="descriptor"

                             value="context://docs/department-form.xml"/>

              <map:parameter name="form-descriptor"

                             value="context://docs/department-form.xml"/>

              <map:generate type="serverpages" src="docs/confirm-dept.xsp"/>

              <map:transform src="stylesheets/apache.xsl"/>

              <map:serialize/>

            </map:act>

            <map:generate type="serverpages" src="docs/{1}-dept.xsp"/>

            <map:transform src="stylesheets/apache.xsl"/>

            <map:serialize/>

          </map:match>

          </map:pipeline>

          </map:pipelines>

          11. Barracuda

          http://barracudamvc.org/Barracuda/index.html

          Barracuda是一個HTML DOM Component + Event/Listener結構的框架。

          根據模板文件或配置文件生成靜態Java類,并在代碼中使用這些生成類,是Barracuda的一大特色。

          Barracuda需要用XMLC項目把所有的HTMLWML模板文件,靜態編譯成DOM結構的Java類,作為頁面組件。XMLC會根據HTML元素的id定義,生成相應DOM結點的簡便操作方法。

           

          Barracuda的事件類也需要用Barracuda Event Builder工具把event.xml編譯成Java類,引入到工程中。Barracuda直接用Java類的繼承關系映射事件之間的父子層次關系。比如,ChildEventParentEvent的子類。

          Barracuda的事件分為兩類:Request EventsControl Events)和Response EventsView Events)。

           

          Barracuda事件處理過程很像Windows系統消息隊列的處理機制。

          (1) Barracuda根據HTTP Request生成Request Event,放入到事件隊列中。

          (2) EventDispatcher檢查事件隊列是否為空,如果為空,結束。如果非空,按照先進先出的方式,從事件隊列中取出一個事件,根據這個事件的類型,選擇并調用最合適的EventListener,參數Event Context包含事件隊列。

           “根據事件類型,選擇最合適的EventListener對象”的過程是這樣的:比如,

          EventDispatcher從時間隊列里取出來一個事件,類型是ChildEventBarracuda首先尋找注冊了監聽ChildEventEventListener,如果找不到,再上溯到ChildEvent的父類ParentEvent,看哪些EventListenerParentEvent感興趣。

          詳細過程參見BarracudaDefaultEventDispatcher類。

          (3) EventListener根據Event Context包含的request信息,調用商業邏輯,獲得結果數據,然后根據不同情況,把新的事件加入到Event Context的事件隊列中。

          (4) 控制交還給EventDispatcher,回到第(2)步。

           

          The End.

          Enjoy.

           

          posted on 2005-07-18 22:26 永琪 閱讀(78) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 绥棱县| 曲靖市| 黎平县| 方正县| 金阳县| 新竹县| 化州市| 无锡市| 石屏县| 科技| 汝城县| 科尔| 七台河市| 大理市| 安康市| 合水县| 仙居县| 重庆市| 柯坪县| 琼结县| 华蓥市| 自治县| 乐山市| 衡东县| 四平市| 灯塔市| 黔东| 桂林市| 廉江市| 红桥区| 博罗县| 霍林郭勒市| 张掖市| 怀化市| 镇宁| 呼和浩特市| 马山县| 定兴县| 鹤岗市| 余江县| 新建县|