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
開發的最重要的基本功是
HTTP
;
Java Web
開發的最重要的基本功是
Servlet Specification
。
HTTP
和
Servlet Specification
對于
Web Server
和
Web Framework
的開發實現來說,是至關重要的協議規范。
應用和剖析開源
Web Framework
,既有助于深入掌握
HTTP & Servlet Specification,
也有助于了解一些現代的
B/S Web
框架設計思想,如
MVC
,事件處理機制,頁面組件,
IoC
,
AOP
等。在這個現代化的大潮中,即使
Servlet
規范本身也不能免俗,不斷引入
Filter
、
Listener
等現代框架設計模式。同是
Sun
公司出品的
JSF
更是如此。
關于
MVC
模型、項目簡介、配置文件、入門示例等基礎知識,網上已經有大量的重復資料信息,本文不再贅述。
文中會提到一些相關的開源項目,和一些編程思想,如有需要,可以用相關的關鍵字在網上搜索,獲取基本的背景知識。
本文力圖言簡意賅,突出重點。著重描述其他資料沒有提到、或很少提到的較重要內容,如運行原理、主流用法,相關知識,關鍵特性等。
1. Java Web
程序工作原理
[
編者按:本部分內容在本期雜志
《Servlet規范簡介》有更詳細介紹
]
Tomcat
的
Server.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
)。所以,
request
的
setAttribute()
和
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
,把處理轉交給
View
(
JSP +TagLib, Velocity, Free Marker, XSL
等)。
View
一般通過
request.getAttribute()
獲得結果數據,并顯示到客戶端。至于是誰把結果數據設置到
request.attribute
里面,有兩種可能:
Action
或
Dispatcher 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 Path
到Action的映射。
如
<action path="/LogonSubmit"
type="app.LogonAction"
... />
?
Struts
的入口Servlet是ActionServlet。
ActionServlet
需要此信息把URL Path調用對應的Action類處理。
在Struts運行期間,一個URL Path,只存在一個對應的Struts Action實例。所有的該URL Path的請求,都經過這同一個Struts Action實例處理。所以Struts Action必須線程安全。
想想看,其實這個要求并不過分,Action只是一個處理程序,不應該保存跨HTTP請求的狀態數據,按理來說,也應該做成線程安全的。
?
(2) Template Name
到View Template Path的映射。
<forward name="success"
path="/pages/Welcome.jsp"/>
?
Action
類返回一個Template Name,ActionServlet根據這個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傳遞的參數只有兩個,request和response。那么只能通過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:select等tag,都包含在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項目上。入口Servlet是WebWork項目中定義的ServletDispatcher,而Action在XWork項目中定義。
XWork Action
接口的execute()方法沒有參數,不像Struts Action那樣接受request, response參數,所以XWork Action能夠脫離Web環境被直接調用,便于單元測試。
這里引入了一個問題。沒有了request參數,那么XWork Action如何獲得request parameters作為輸入數據?又通過什么橋梁(Struts用request.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填到YourAction的productId里面,然后執行execute()方法,JSP里的語句<ww:property value=“productName”>會把YourAction的productName顯示在頁面上。
?
如果一個Web Framework采用了這種屏蔽Action的request, response參數的設計方式,一般也同時會采用這種Action和輸入輸出數據結合成一體的解決方式。類似的情形也存在于Tapestry和Maverick中,后面會講到。
當WebWork ServletDispatcher接收到HTTP Request的時候,首先把所有相關的信息(包括request, response, session,
servlet config, servelt context,
所有request參數)等存放到AcationContext中,然后根據Interceptor配置信息,生成一個YourAction的動態代理類對象。實際上運行的正是這個代理對象,如同Servlet Filter的工作機制一般,所有注入的Interceptor方法會先于Actio方法運行。
我們來看一下Action和Interceptor的地位: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
能夠在Package和Action級別上,進行截獲處理。
Servlet Filter
能夠在URL Patten級別上,進行截獲處理。雖然實際上,Servlet Filter截獲的是Servlet,但某些情況下,可以達到和截獲一批Action的同樣效果。
比如,在Web Work中,我們可以為所有admin package的Action,加入一個Interceptor,當檢查到當前Session的用戶沒有admin權限時,統一返回一個警告頁面:您沒有足夠的權限執行這個操作。
我們看到也可以為所有URL Pattern為“admin/*.action”的URL定義一個Servlet Filter,當檢查到當前Session的用戶沒有admin權限時,統一返回一個警告頁面:您沒有足夠的權限執行這個操作。
?
WebWork
的Interceptor配置是相當靈活的,相當于對Action實現了AOP。Interceptor相當于Aspect,基類AroundInterceptor的before(), after()方法相當于Advice。
另外,XWork也提供了從XML配置文件裝配Component的機制,相當于實現了對于Component的IoC。
提到AOP和IoC,順便多講兩句。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,JSP。WebWork還提供了自己的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
參數,映射到
Action
(
Tapestry
里面稱為
Page
)的方法,并把
request
參數映射為
Page
方法需要的參數,進行正確的調用。就這樣,
Tapestry
不僅把輸入輸出數據,而且把事件方法也綁定到了
Page
上面。
在
Tapestry
框架中,
Action
的概念已經非常模糊,而換成了
Page
的概念。而
Tapestry Page
是擁有屬性和事件的頁面組件,其中的事件處理部相當于
Action
的職責,而屬性部分起著
Model
的作用。
除了使用
Page
和其它的
Tapestry
頁面組件,用戶也可以自定義頁面組件。
?
這種頁面組件
/
屬性事件的編程模型,受到一些程序員的歡迎。當然,這種編程模型并不是沒有代價的,每個
Tapestry
模板文件都需要一個對應的
.page
文件。這些
.page
文件定義了頁面組件的屬性、事件、
Validator
等信息。
?
我們來看一下
B/S
結構中,組件的屬性、事件和
HTTP Request
綁定的基本原理。一個能夠發出請求的頁面組件(比如
Link
和
Button
),在輸出自己的
HTML
的時候,需要輸出一些特殊的信息來標志本組件的屬性
/
事件,這樣下次
HTTP Request
來的時候,會把這些信息帶回來,以便
Web Framework
加以辨認識別,發給正確的
Page Component
處理。
這些特殊信息通常包含在
URL
參數或
Hidden Input
里面,必要的時候,還需要生成一些
Java Script
。
Tapestry
,
Echo
,
JSF
都是這種原理。
Tapestry
的例子如下:
<a href="#" jwcid="@DirectLink" parameters="ognl:currentItem.itemId" listener="ognl:listeners.showItem">
[
編者按:OGNL是一種利用java對象setter和getter方法來訪問其屬性的表達式語言,Tepestry項目及很多項目使用了該技術。更詳細鏈接http://www.ognl.org/]
JSF
用
TagLib
實現頁面組件,也提供了類似的
CommandLink
和
CommandButton Tag
。其中對應
Tapestry listener
的
Tag
屬性是
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
映射的配置文件。
Echo
的
Action
就是實現了
ActionListener
接口(參數為
ActionEvent
)的
Servlet
(繼承
EchoServer
類)。
所以,
Echo
直接由
Web Server
根據
web.xml
配置的
URL -> Servlet
的映射,進行轉發控制。
?
Echo
也沒有明顯的
View
層,
Echo
在頁面組件方面走得更遠,所有的
HTML
和
JavaScript
都由框架生成。你不必(也沒有辦法)寫
HTML
,只需要(也只能)在
Java
代碼中按照類似
Swing
編程方式,生成或操作用戶界面。用戶也可以定制自己的
Echo
組件。
Echo
的
UI Component
的實現,采用了兩個重要的模式。一個是
Peer
(
Component -> ComponentPeer
)模式,一個是
UI Component -> Renderer
模式。
雖然
Echo
的
API
更類似于
Swing
,但實現上卻采用更接近于
AWT
的
Peer
模式。每個
Component
類(代表抽象的組件,比如
Button
),都有一個對應的
ComponentPeer
類(代表實際的組件,比如
windows
桌面的
Button
,
Linux
桌面的
Button
,
HTML 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
有可能對應
Model
,
Event
,
Listener
。
Tag
包含
componentType
和
rendererType
兩個屬性,用來選擇對應的的
UI Component
和
Renderer
。
JSF
的應用核心無疑是
JSF TagLib
。
JSF TagLib
包含了對應所有重要
HTML
元素的
Tag
,而且
Input Tag
可以直接包含
Validator Tag
或者
Validator
屬性,來定義驗證手段。
?
我們通過
JSF
攜帶的
cardemo
例子,來看
JSF
的處理流程。
(1) carDetail.jsp
有如下內容:
<h:commandButton action="#{carstore.buyCurrentCar}" value="#{bundle.buy}" />
可以看到,這個
button
的
submit action
和
carstore.buyCurrentCar
方法綁定在一起。我們在
Tapestry
里面曾經看到過類似的情景。
?
(2) carstore
在
faces-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 Component
的
Model-Event-Listener
。
也許設計者認為,眾多龐雜的模式能夠保證
JSF
成為一個成功的框架。
Portal
開源項目
eXo
就是建立在
JSF
框架上。
?
可以看出這樣一個趨勢,現代
Web Framework
認為
B/S
結構的無狀態特性和
HTML
界面是對編程來說是需要極力掩蓋的一個缺陷,所以盡量模擬
C/S
結構的組件和事件機制,以吸引更多的程序員。
7. Maverick
http://mav.sourceforge.net/
Maverick
是一個輕量而完備的
MVC Model 2
框架。
Maverick
的
Action
不叫
Action
,直截了當的稱作
Controller
。
Controller
只接受一個
ControllerContext
參數。
request
,
response, servlet config, servelt context
等輸入信息都包裝在
ControllerContext
里面,而且
Model
也通過
ControllerContext
的
model
屬性返回。整個編程結構清晰而明快,令人贊賞。
但這個世界上難有十全十美的事情,由于
ControllerContext
只有一個
model
屬性可以傳遞數據,程序員必須把所有需要的數據都打包在一個對象里面設置到
model
屬性里。這種麻煩自然而然會導致這樣的可能用法,直接把
Controller
本身設置為
model
,這又回到了
Controller(Action)
和
Model
一體的老路。
?
前面講到,
WebWork
也把所有的輸入信息都包裝在
ActionContext
里面,但
Action
并沒有權力獲取。而在
Maverick
中,
Controller
對于
ControllerContext
擁有全權的控制,兩者地位不可同日而語。當然,由于參數
ControllerContext
包含
request
,
reponse
之類信息,這也意味著,
Maverick Controller
不能像
WebWork Action
那樣脫離
Web
環境獨立運行。
當然,這也并不意味著任何結構性缺陷。程序的結構由你自己控制,你完全可以把需要
Unit Test
的那部分從
Web
環境脫離開來,放到
Business
層。
如同
WebWork
,
Maverick
直接支持所有的主流
View
。
Maverick
的配置文件采
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
,準確地稱做
Controller
;
Controller
接收
request, response
參數,干脆利落地返回
ModelAndView
(其中的
Model
不是
Object
類型,而是
Map
類型)。
其它的
Web Framework
中,
Action
返回值一般都只是一個
View Name
;
Model
則需要通過其它的途徑(如
request.attribute
,
Context
參數,或
Action
本身的屬性數據)傳遞上去。
?
Spring
以一招
IoC
名滿天下,其
AOP
也方興未艾。“
Spring
出品,必屬精品”的觀念已經深入人心。我這里多說也無益,強烈建議讀者去閱讀
Spring Doc & Sample & Code
本身。
9. Turbine
http://jakarta.apache.org/turbine/
Turbine
是一個提供了完善權限控制的堅實框架(
Fulcrum
子項目是其基石)。
Turbine
的個人用戶不多,但不少公司用戶選擇
Turbine
作為框架,開發一些嚴肅的應用(我并沒有說,用其它框架開發的應用就不嚴肅
^_^
)。
Portal
開源項目
JetSpeed
建立在
Turbine
上。
Turbine
用
RunData
來傳遞輸入輸出數據。如同
Maverick
的
ControllerContext
,
RunData
是整個
Turbine
框架的數據交換中心。除了
request, response
等基本信息,
RunData
直接包括了
User/ACL
等權限控制相關的屬性和方法,另外還包括
Action Name
和
Target Template Name
等定位屬性。
Module
是
Turbine
里面除了
RunData
之外的又一個核心類,是
Turbine
框架的基本構件,
Action
是
Module
,
Screen
也是
Module
。
Turbine
提供了
LoginUser
和
LogoutUser
兩個
Action
作為整個系統的出入口。而其余流量的權限控制則由類似于
Servlet Filter
機制的
Pipeline
控制。
Turbine Pipeline
的編程模型和
Servlet Filter
一模一樣:
Turbine Pipeline
的
Valve
就相當于
Servlet Filter
,而
ValveContext
則相當于
Filter Chain
。還有更相近的例子,
Tomcat
源代碼里面也有
Valve
和
ValueContext
兩個類,不僅編程模型一樣,而且名字也一樣。
?
權限控制貫穿于
Turbine
框架的始終。要用好
Turbine
,首先要通曉子項目
Fulcrum
的
Security
部分的權限實現模型。
Fulcrum Security
的權限實體包括四個
-- User, Group, Role, Permission
。
實體之間包含
{Role
,
Permission}
和
{ Group, User, Role}
兩組關系。
{Role
,
Permission}
是多對多的關系,一個
Role
可以具有各種
Permission
;
{ Group, User, Role}
之間是多對多的關系,一個
Group
可包含多個
User
,并可以給
User
分配不同的
Role
。
權限模型的實現同樣采用
Peer
模式,
Entity -> EntityPeer, Entity -> ManagerPeer
。
Entity
和
EntityManger
代表抽象的模型概念,而
EntityPeer
和
ManagerPeer
代表具體的實現。
用戶可以根據模型,提供不同的實現,比如,用內存結構中實現,用數據表結構實現,與
Windows NT
權限驗證機制結合,與
OSWorkflow
的權限控制模型結合,等等。其中,用數據表結構實現,又可以選擇用
Torque
實現,或者用
Hibernate
實現。(
Torque
是
Turbine
的
O/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_USER
,
TURBINE_ROLE
,
TURBINE_GROUP
,
TURBINE_PERMISSION
,
TURBINE_ROLE_PERMISSION
,
TURBINE_USER_GROUP_ROLE
。
?
10. Cocoon
http://cocoon.apache.org
Cocoon
項目是一個叫好不叫做的框架。采用
XML + XSLT Pipeline
機制,
Java
程序只需要輸出
XML
數據,
Cocoon
框架調用
XSL
文件把
XML
數據轉換成
HTML
、
WML
等文件。
Cocoon
強大靈活的
XSL Pipeline
配置功能,
XSLT
的內容
/
顯示分離的承諾,一直吸引了不少程序員
fans
。怎奈天不從人愿,由于復雜度、速度瓶頸、
XSL
學習難度等問題的限制,
Cocoon
一直主要限于網站發布出版領域,向
CMS
和
Portal
方向不斷發展。另外,
Cocoon
開發了
XSP
腳本和
Cocoon Form
技術。
Cocoon
的
sitemap.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
項目把所有的
HTML
或
WML
模板文件,靜態編譯成
DOM
結構的
Java
類,作為頁面組件。
XMLC
會根據
HTML
元素的
id
定義,生成相應
DOM
結點的簡便操作方法。
?
Barracuda
的事件類也需要用
Barracuda Event Builder
工具把
event.xml
編譯成
Java
類,引入到工程中。
Barracuda
直接用
Java
類的繼承關系映射事件之間的父子層次關系。比如,
ChildEvent
是
ParentEvent
的子類。
Barracuda
的事件分為兩類:
Request Events
(
Control Events
)和
Response Events
(
View Events
)。
?
Barracuda
事件處理過程很像
Windows
系統消息隊列的處理機制。
(1) Barracuda
根據
HTTP Request
生成
Request Event
,放入到事件隊列中。
(2) EventDispatcher
檢查事件隊列是否為空,如果為空,結束。如果非空,按照先進先出的方式,從事件隊列中取出一個事件,根據這個事件的類型,選擇并調用最合適的
EventListener
,參數
Event Context
包含事件隊列。
?
“根據事件類型,選擇最合適的
EventListener
對象”的過程是這樣的:比如,
EventDispatcher
從時間隊列里取出來一個事件,類型是
ChildEvent
;
Barracuda
首先尋找注冊了監聽
ChildEvent
的
EventListener
,如果找不到,再上溯到
ChildEvent
的父類
ParentEvent
,看哪些
EventListener
對
ParentEvent
感興趣。
詳細過程參見
Barracuda
的
DefaultEventDispatcher
類。
(3) EventListener
根據
Event Context
包含的
request
信息,調用商業邏輯,獲得結果數據,然后根據不同情況,把新的事件加入到
Event Context
的事件隊列中。
(4)
控制交還給
EventDispatcher
,回到第
(2)
步。
?
The End.
Enjoy.
?