本文介紹
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
程序工作原理
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">
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.
posted on 2006-08-16 19:59
阿成 閱讀(173)
評論(0) 編輯 收藏