本文介紹Java Web Framework的基本工作原理,和一些常用的開(kāi)源Web MVC Framework(Struts, Web Work, Tapestry, Echo, JSF, Maverick, Spring MVC, Turbine, Cocoon, Barracuda)。
Web開(kāi)發(fā)的最重要的基本功是HTTP;Java Web開(kāi)發(fā)的最重要的基本功是Servlet Specification。HTTP和Servlet Specification對于Web Server和Web Framework的開(kāi)發(fā)實(shí)現來(lái)說(shuō),是至關(guān)重要的協(xié)議規范。
應用和剖析開(kāi)源Web Framework,既有助于深入掌握HTTP & Servlet Specification, 也有助于了解一些現代的B/S Web框架設計思想,如MVC,事件處理機制,頁(yè)面組件,IoC,AOP等。在這個(gè)現代化的大潮中,即使Servlet規范本身也不能免俗,不斷引入Filter、Listener等現代框架設計模式。同是Sun公司出品的JSF更是如此。
關(guān)于MVC模型、項目簡(jiǎn)介、配置文件、入門(mén)示例等基礎知識,網(wǎng)上已經(jīng)有大量的重復資料信息,本文不再贅述。
文中會(huì )提到一些相關(guān)的開(kāi)源項目,和一些編程思想,如有需要,可以用相關(guān)的關(guān)鍵字在網(wǎng)上搜索,獲取基本的背景知識。
本文力圖言簡(jiǎn)意賅,突出重點(diǎn)。著(zhù)重描述其他資料沒(méi)有提到、或很少提到的較重要內容,如運行原理、主流用法,相關(guān)知識,關(guān)鍵特性等。
Tomcat的Server.xml文件中定義了網(wǎng)絡(luò )請求路徑到主機本地文件路徑的映射。比如,<context path="/yourapp" docBase="yourapp_dir/webapp"/>
我們來(lái)看一下,一個(gè)HTTP Request-Response Cycle的處理過(guò)程。
HTTP Request URL一般分為三段:host, context, path。
如http://yourhost/yourapp/en/index.html這個(gè)URL,分為host=yourhost, context=yourapp, path=en/index.html三段。其中,Context部分由request.getContext()獲得,path部分由request.getServletPath()獲得(返回結果是“/en/index.html”)。
yourhost主機上運行的Tomcat Web Server接收到這個(gè)URL,根據Context定義,把yourapp這個(gè)網(wǎng)絡(luò )路徑映射為yourapp_dir/webapp,并在此目錄下定位en/index.html這個(gè)文件,返回到客戶(hù)端。
如果我們這個(gè)URL更換為http://yourhost/yourapp/en/index.jsp,這個(gè)時(shí)候Tomcat會(huì )試圖把yourapp_dir/webapp/en/index.jsp文件編譯成Servlet,并調用運行這個(gè)Servlet。
我們再把這個(gè)URL更換為http://yourhost/yourapp/en/index.do。
注意,戲劇化的事情就發(fā)生在這個(gè)時(shí)候,Servlet規范中最重要的類(lèi)RequestDispatcher登場(chǎng)了。RequestDispatcher根據WEB-INF/web.xml配置文件的定義,調用對應的Servlet來(lái)處理en/index.do這個(gè)路徑。
假設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會(huì )調用yourapp.DispatchServlet類(lèi)處理這個(gè)路徑。
如果web.xml沒(méi)有定義對應en/index.do這個(gè)路徑的Servlet,那么Tomcat返回“您請求的資源不存在”。
RequestDispatcher用于Web Server中,也可以用于應用程序中進(jìn)行處理轉向,資源定位。比如,我們在處理en/index.do的代碼中調用,
request.getRequestDispatcher(“cn/index.jsp”).forward(request, response), 就可以轉交另外的資源cn/index.jsp來(lái)處理。
幾乎所有的Web Framework都需要定義自己的Dispatch作用的Servlet,并調用RequestDispatcher進(jìn)行轉向處理。
閱讀Web Framework源代碼,有兩條主要線(xiàn)索,(1)根據web.xml找到對應的Servlet類(lèi);(2)搜索包含“RequestDispatcher”詞的代碼文件。
我們看到,request, response 這兩個(gè)參數,被RequestDispatcher在各種Servlet之間傳來(lái)傳去(JSP也是Servlet)。所以,request的setAttribute()和getAttribute()方法是Servlet之間傳送數據的主要方式。
在MVC結構中,一般的處理流程如下:
處理HTTP Request的基本單位一般稱(chēng)為Action,是一個(gè)比Servlet輕量得多的接口定義,通常只有一兩個(gè)方法,如execute(perform), validate等。
我們知道,URL->Servlet映射,定義在Web.xml配置文件里,但MVC框架通常會(huì )有另外一個(gè)定義URL-> Action映射的配置文件。
入口Dispatcher Servlet根據URL -> Action的映射關(guān)系,把請求轉發(fā)給Action。
Action獲得輸入參數,調用商業(yè)邏輯,并把結果數據和View標識給(Model & View)返回給Dispatcher Servlet。
Dispatcher Servlet根據這個(gè)View 標識,定位相應的View Template Path,把處理轉交給View(JSP +TagLib, Velocity, Free Marker, XSL等)。
View一般通過(guò)request.getAttribute()獲得結果數據,并顯示到客戶(hù)端。至于是誰(shuí)把結果數據設置到request.attribute里面,有兩種可能:Action或Dispatcher Servlet。
Struts是目前用戶(hù)群最大、開(kāi)發(fā)廠(chǎng)商支持最多的開(kāi)源Web Framework。
Struts勞苦功高,為普及MVC框架作出了不可磨滅的貢獻。顯赫的聲望,趨于老化的厚重結構,令Struts成為很多現代Web Framework參照、挑戰的目標。
Struts應用主要包括3件事情: 配置struts-config.xml文件,實(shí)現Action類(lèi),實(shí)現View;還有一些高級擴展用法。下面分別講述。
1. 配置struts-config.xml文件:
Struts支持多級配置文件,具體用法和限制,詳見(jiàn)Struts文檔。這里只討論struts-config.xml主流配置的內容。:-)
(1) URL Path到Action的映射。
如<action path="/LogonSubmit" type="app.LogonAction" ... />
Struts的入口Servlet是ActionServlet。
ActionServlet需要此信息把URL Path調用對應的Action類(lèi)處理。
在Struts運行期間,一個(gè)URL Path,只存在一個(gè)對應的Struts Action實(shí)例。所有的該URL Path的請求,都經(jīng)過(guò)這同一個(gè)Struts Action實(shí)例處理。所以Struts Action必須線(xiàn)程安全。
想想看,其實(shí)這個(gè)要求并不過(guò)分,Action只是一個(gè)處理程序,不應該保存跨HTTP請求的狀態(tài)數據,按理來(lái)說(shuō),也應該做成線(xiàn)程安全的。
(2) Template Name到View Template Path的映射。
<forward name="success" path="/pages/Welcome.jsp"/>
Action類(lèi)返回一個(gè)Template Name,ActionServlet根據這個(gè)Template Name獲得對應的View Template Path,然后調用
request.getRequestDispatcher(“View Template Path”),把處理轉向路徑對應的Servlet。在這個(gè)例子中,是轉向/pages/Welcome.jsp編譯后的Servlet。
我們來(lái)看一個(gè)一個(gè)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>
這時(shí),request.getRequestDispatcher(“/pages/Welcome.vm”)會(huì )調用VelocityViewServlet,由VelocityViewServlet負責裝并驅動(dòng)運行/pages/Welcome.vm這個(gè)模板文件。
這里面有一個(gè)問(wèn)題,如果調用的是DispatchRequester.include()方法,那么如何才能把pages/Welcome.vm傳給VelocityViewServlet呢?
如前所說(shuō),RequestDispatcher傳遞的參數只有兩個(gè),request和response。那么只能通過(guò)request attribute。正是為了解決這個(gè)問(wèn)題,Servlet2.3規范之后,加入了javax.servlet.include.servlet_path這個(gè)屬性。
參見(jiàn)VelocityViewServlet的代碼(velocity-tool開(kāi)源項目)
// 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至關(guān)重要。
(3) Form Bean的定義
如<form-bean name="logonForm"type="app.LogonForm"/>
Struts Form Bean需要繼承ActionForm類(lèi)。Form Bean類(lèi),主要有三個(gè)作用:
[1]根據bean的定義,利用reflection機制,自動(dòng)把request參數轉化為需要的數據類(lèi)型,填入到bean的屬性當中。ActionForm類(lèi)名中雖然有Form這個(gè)詞,但不僅能夠獲取Form提交后的HTTP Post參數,也可以獲取URL后綴的HTTP Get參數。
[2]輸入驗證。用戶(hù)可以配置validation.xml,定義各屬性的驗證規則。
[3]當作View Object來(lái)用。用戶(hù)需要熟練掌握Struts HTML TagLib的用法,才能把Form Bean的屬性正確顯示出來(lái)。
(4)其他定義。詳見(jiàn)Struts文檔。不再贅述。
2.實(shí)現Action。
Action類(lèi)從Form Bean或直接從request中獲得輸入參數,調用商業(yè)邏輯,把結果數據(也許會(huì )包裝成View Object),用request.setAttribute()放到request中,最后返回一個(gè)用ForwardMapping類(lèi)包裝的Template Name。
3.實(shí)現View。
Struts View的標準實(shí)現方法是JSP + Struts TagLib,其中最重要的就是Struts HTML TagLib。
html:form tag則是整個(gè)HTML Tag的核心,其它的如html:input, html:select等tag,都包含在html:form tag里面。
html:form tag用來(lái)映射Form Bean(也可以通過(guò)適當定義,映射其他的bean,但使用上會(huì )有很多麻煩)。html:form tag包含的其他Struts html tag用來(lái)映射Form Bean的屬性。
Struts Bean TagLib的用法比較臃腫,一般情況下可以用JSTL代替。當然,如果需要用到bean:message tag實(shí)現國際化,那又另當別論。
Struts Tile TagLib用于頁(yè)面布局。開(kāi)源Portal項目Liferay使用了Struts Tile TagLib做為布局控制。
4.高級擴展用法
用戶(hù)可以重載Struts的一些控制類(lèi),引入自己的一些定制類(lèi)。詳見(jiàn)Struts文檔。
本文不是Struts專(zhuān)題,只講述最重要的主流用法,其它邊邊角角的,不再贅述。
http://www.opensymphony.com/webwork/
WebWork由于靈活的可插拔特性,受到很多資深程序員的歡迎。似乎很有可能大肆流行起來(lái)。
WebWork項目建立在XWork項目上。入口Servlet是WebWork項目中定義的ServletDispatcher,而Action在XWork項目中定義。
XWork Action接口的execute()方法沒(méi)有參數,不像Struts Action那樣接受request, response參數,所以XWork Action能夠脫離Web環(huán)境被直接調用,便于單元測試。
這里引入了一個(gè)問(wèn)題。沒(méi)有了request參數,那么XWork Action如何獲得request parameters作為輸入數據?又通過(guò)什么橋梁(Struts用request.setAttribute)把結果數據傳送到View層?
在Web Work中,只能通過(guò)Action本身的getter, setter屬性來(lái)傳送輸入參數和輸出結果。
比如,我們有這樣一個(gè)實(shí)現了XWork Action接口的類(lèi),
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”;
}
}
這個(gè)類(lèi)里面的productId將接受request輸入參數,productName是輸出到頁(yè)面顯示的結果。
比如,這樣的請求,http://yourhost/yourapp/MyAction.action?productId=1
Web Work會(huì )把1填到YourAction的productId里面,然后執行execute()方法,JSP里的語(yǔ)句<ww:property value=“productName”>會(huì )把YourAction的productName顯示在頁(yè)面上。
如果一個(gè)Web Framework采用了這種屏蔽Action的request, response參數的設計方式,一般也同時(shí)會(huì )采用這種Action和輸入輸出數據結合成一體的解決方式。類(lèi)似的情形也存在于Tapestry和Maverick中,后面會(huì )講到。
當WebWork ServletDispatcher接收到HTTP Request的時(shí)候,首先把所有相關(guān)的信息(包括request, response, session, servlet config, servelt context, 所有request參數)等存放到AcationContext中,然后根據Interceptor配置信息,生成一個(gè)YourAction的動(dòng)態(tài)代理類(lèi)對象。實(shí)際上運行的正是這個(gè)代理對象,如同Servlet Filter的工作機制一般,所有注入的Interceptor方法會(huì )先于Actio方法運行。
我們來(lái)看一下Action和Interceptor的地位:Action沒(méi)有參數,無(wú)法獲得ActionContext;而Interceptor接受的ActionInvoication參數擁有包括ActionContext在內的所有重要信息。
這種權力分配的不平等,注定了Action的作用非常有限,只限于調用商業(yè)邏輯,然后返回一個(gè)成功與否標志。所有與外部Web世界打交道、協(xié)調內部工作流程的重擔,都責無(wú)旁貸地落在Interceptor的肩上。
我們可以設想一個(gè)極端的例子。我們聲明一批不做任何事情的空Action,我們只是需要它們的空殼類(lèi)名;我們制作一批對應的Interceptor,所有的轉發(fā)控制、商業(yè)邏輯都在Interceptor上實(shí)現,然后把Interceptor都注入到對應的空Action。這在理論上是完全可行的。
在Web海洋的包圍中,Action可少,Interceptor不可少。Action是一個(gè)孤島,如果沒(méi)有外來(lái)盟友Interceptor的協(xié)助,只能在自己的小范圍內獨立作戰(比如Unit Test),而對整體大局的作戰目標無(wú)法產(chǎn)生影響。
下面我們來(lái)看一下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里面的相關(guān)定義如下:
<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()方法的前后,會(huì )被
defaultStack所定義的三個(gè)Intercepter截獲。這些Interceptor的任務(wù)之一就是把輸入參數設置到Action的對應屬性當中。
如果我們需要加入對YourAction的屬性的驗證功能,只要把上述定義中的validation Interceptor加入到defaultStack中就可以了。當然,實(shí)際工作還沒(méi)有這么簡(jiǎn)單,一般來(lái)說(shuō),還要為每個(gè)進(jìn)行屬性驗證的Action的都配置一份validation.xml。
XWork Interceptor能夠在Package和Action級別上,進(jìn)行截獲處理。
Servlet Filter能夠在URL Patten級別上,進(jìn)行截獲處理。雖然實(shí)際上,Servlet Filter截獲的是Servlet,但某些情況下,可以達到和截獲一批Action的同樣效果。
比如,在Web Work中,我們可以為所有admin package的Action,加入一個(gè)Interceptor,當檢查到當前Session的用戶(hù)沒(méi)有admin權限時(shí),統一返回一個(gè)警告頁(yè)面:您沒(méi)有足夠的權限執行這個(gè)操作。
我們看到也可以為所有URL Pattern為“admin/*.action”的URL定義一個(gè)Servlet Filter,當檢查到當前Session的用戶(hù)沒(méi)有admin權限時(shí),統一返回一個(gè)警告頁(yè)面:您沒(méi)有足夠的權限執行這個(gè)操作。
WebWork的Interceptor配置是相當靈活的,相當于對Action實(shí)現了AOP。Interceptor相當于Aspect,基類(lèi)AroundInterceptor的before(), after()方法相當于Advice。
另外,XWork也提供了從XML配置文件裝配Component的機制,相當于實(shí)現了對于Component的IoC。
提到AOP和IoC,順便多講兩句。Spring AOP能夠截獲所有Interface,不限于某個(gè)特定接口;Spring框架支持所有類(lèi)型的IoC,不限于某種特定類(lèi)型。
要知道,AOP, IoC可是現在最時(shí)髦的東西,一定不要錯過(guò)啊。:D
相關(guān)概念導讀(如果需要,請用如下關(guān)鍵字搜索網(wǎng)絡(luò )):
AOP -- Aspect Oriented Programming -- 面向方面編程。
IoC – Inversion of Control --控制反轉
Dynamic Proxy -- 動(dòng)態(tài)代理,JDK1.4引入的特性。還可以進(jìn)一步參考CGLib, ASM等開(kāi)源項目。
WebWork直接支持所有主流View -- XSL,Velocity, FreeMarker,JSP。WebWork還提供了自己的TagLib。“直接支持”的意思是說(shuō),不用像Struts那樣,使用Velocity的時(shí)候,還需要引入輔助橋梁Velocity-tool。
WebWork中用到一種功能和XPath類(lèi)似的對象尋徑語(yǔ)言ONGL,是一個(gè)開(kāi)源項目。ONGL同樣用在下面要介紹的Tapestry項目中。
Opensymphony下還有一個(gè)SiteMesh項目,通過(guò)Servlet Filter機制控制布局??梢院?/span>WebWork組合使用。
http://jakarta.apache.org/tapestry/
Tapestry近來(lái)突然火了起來(lái),令我感到吃驚。也許是JSF帶來(lái)的Page Component風(fēng)潮令人們開(kāi)始關(guān)注和追逐Tapestry。
Tapestry的重要思想之一就是Page Component。
前面講到,XWork能夠自動(dòng)把request參數映射到Action的屬性當中。Tapestry走得更遠,甚至能夠根據request參數,映射到Action(Tapestry里面稱(chēng)為Page)的方法,并把request參數映射為Page方法需要的參數,進(jìn)行正確的調用。就這樣,Tapestry不僅把輸入輸出數據,而且把事件方法也綁定到了Page上面。
在Tapestry框架中,Action的概念已經(jīng)非常模糊,而換成了Page的概念。而Tapestry Page是擁有屬性和事件的頁(yè)面組件,其中的事件處理部相當于Action的職責,而屬性部分起著(zhù)Model的作用。
除了使用Page和其它的Tapestry頁(yè)面組件,用戶(hù)也可以自定義頁(yè)面組件。
這種頁(yè)面組件/屬性事件的編程模型,受到一些程序員的歡迎。當然,這種編程模型并不是沒(méi)有代價(jià)的,每個(gè)Tapestry模板文件都需要一個(gè)對應的.page文件。這些.page文件定義了頁(yè)面組件的屬性、事件、Validator等信息。
我們來(lái)看一下B/S結構中,組件的屬性、事件和HTTP Request綁定的基本原理。一個(gè)能夠發(fā)出請求的頁(yè)面組件(比如Link和Button),在輸出自己的HTML的時(shí)候,需要輸出一些特殊的信息來(lái)標志本組件的屬性/事件,這樣下次HTTP Request來(lái)的時(shí)候,會(huì )把這些信息帶回來(lái),以便Web Framework加以辨認識別,發(fā)給正確的Page Component處理。
這些特殊信息通常包含在URL參數或Hidden Input里面,必要的時(shí)候,還需要生成一些Java Script。Tapestry,Echo,JSF都是這種原理。
Tapestry的例子如下:
<a href="#" jwcid="@DirectLink" parameters="ognl:currentItem.itemId" listener="ognl:listeners.showItem">
JSF用TagLib實(shí)現頁(yè)面組件,也提供了類(lèi)似的CommandLink和CommandButton Tag。其中對應Tapestry listener的Tag屬性是action。后面會(huì )講解。
Tapestry的模板標簽是HTML標簽的擴展,具有良好的“所見(jiàn)即所得”特性,能夠直接在瀏覽器中正確顯示,這也是Tapestry的一個(gè)亮點(diǎn)。
http://sourceforge.net/projects/echo
Echo提供了一套類(lèi)似Swing的頁(yè)面組件,直接生成HTML。
從程序員的角度看來(lái),用Echo編寫(xiě)Web程序,和用Swing編寫(xiě)Applet一樣,屬于純面向組件事件編程,編程模型也以Event/Listener結構為主體。
Echo沒(méi)有Dispatcher Servlet,也沒(méi)有定義URL->Action映射的配置文件。
Echo的Action就是實(shí)現了ActionListener接口(參數為ActionEvent)的Servlet(繼承EchoServer類(lèi))。
所以,Echo直接由Web Server根據web.xml配置的URL -> Servlet的映射,進(jìn)行轉發(fā)控制。
Echo也沒(méi)有明顯的View層,Echo在頁(yè)面組件方面走得更遠,所有的HTML和JavaScript都由框架生成。你不必(也沒(méi)有辦法)寫(xiě)HTML,只需要(也只能)在Java代碼中按照類(lèi)似Swing編程方式,生成或操作用戶(hù)界面。用戶(hù)也可以定制自己的Echo組件。
Echo的UI Component的實(shí)現,采用了兩個(gè)重要的模式。一個(gè)是Peer(Component -> ComponentPeer)模式,一個(gè)是UI Component -> Renderer模式。
雖然Echo的API更類(lèi)似于Swing,但實(shí)現上卻采用更接近于AWT的Peer模式。每個(gè)Component類(lèi)(代表抽象的組件,比如Button),都有一個(gè)對應的ComponentPeer類(lèi)(代表實(shí)際的組件,比如windows桌面的Button,Linux桌面的Button,HTML Button等)。
先別急,這個(gè)事情還沒(méi)有完。雖然ComponentPeer落實(shí)到了具體的界面控件,但是它還是舍不得顯示自己,進(jìn)一步把顯示工作交給一個(gè)Renderer來(lái)執行。
比如,在Echo里面,Button類(lèi)對應一個(gè)ButtonUI(繼承了ComponentPeer)類(lèi),而這個(gè)ButtonUI類(lèi)會(huì )把最終顯示交給ButtonRender來(lái)處理。
據說(shuō)多了這么一步,能夠讓顯示控制更加靈活豐富。比如,同一個(gè)Renderer可以處理不同的UI Component,同一個(gè)UI Component也可以交給不同的Renderer處理。
JSF的頁(yè)面組件也采用了UI Component -> Renderer模式,后面會(huì )講到。
http://java.sun.com/j2ee/javaserverfaces/index.jsp
http://wwws.sun.com/software/communitysource/jsf/download.html download source
JSF的中心思想也是頁(yè)面組件/屬性事件。一般來(lái)說(shuō),JSF的頁(yè)面組件是一個(gè)三件套{ UI Component, Tag, Renderer}。
UI Component有可能對應Model,Event,Listener。Tag包含componentType和rendererType兩個(gè)屬性,用來(lái)選擇對應的的UI Component和Renderer。
JSF的應用核心無(wú)疑是JSF TagLib。JSF TagLib包含了對應所有重要HTML元素的Tag,而且Input Tag可以直接包含Validator Tag或者Validator屬性,來(lái)定義驗證手段。
我們通過(guò)JSF攜帶的cardemo例子,來(lái)看JSF的處理流程。
(1) carDetail.jsp有如下內容:
<h:commandButton action="#{carstore.buyCurrentCar}" value="#{bundle.buy}" />
可以看到,這個(gè)button的submit action和carstore.buyCurrentCar方法綁定在一起。我們在Tapestry里面曾經(jīng)看到過(guò)類(lèi)似的情景。
(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類(lèi)中的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)于是轉到頁(yè)面confirmChoices.jsp。
除了Interceptor之外,JSF幾乎包含了現代Web Framework應該具備的所有特性:頁(yè)面組件,屬性事件,IoC (ManagedBean),Component -> Renderer,類(lèi)似于Swing Component的Model-Event-Listener。
也許設計者認為,眾多龐雜的模式能夠保證JSF成為一個(gè)成功的框架。Portal開(kāi)源項目eXo就是建立在JSF框架上。
可以看出這樣一個(gè)趨勢,現代Web Framework認為B/S結構的無(wú)狀態(tài)特性和HTML界面是對編程來(lái)說(shuō)是需要極力掩蓋的一個(gè)缺陷,所以盡量模擬C/S結構的組件和事件機制,以吸引更多的程序員。
Maverick是一個(gè)輕量而完備的MVC Model 2框架。Maverick的Action不叫Action,直截了當的稱(chēng)作Controller。
Controller只接受一個(gè)ControllerContext參數。request,response, servlet config, servelt context等輸入信息都包裝在ControllerContext里面,而且Model也通過(guò)ControllerContext的model屬性返回。整個(gè)編程結構清晰而明快,令人贊賞。
但這個(gè)世界上難有十全十美的事情,由于ControllerContext只有一個(gè)model屬性可以傳遞數據,程序員必須把所有需要的數據都打包在一個(gè)對象里面設置到model屬性里。這種麻煩自然而然會(huì )導致這樣的可能用法,直接把Controller本身設置為model,這又回到了Controller(Action)和Model一體的老路。
前面講到,WebWork也把所有的輸入信息都包裝在ActionContext里面,但Action并沒(méi)有權力獲取。而在Maverick中,Controller對于ControllerContext擁有全權的控制,兩者地位不可同日而語(yǔ)。當然,由于參數ControllerContext包含request,reponse之類(lèi)信息,這也意味著(zhù),Maverick Controller不能像WebWork Action那樣脫離Web環(huán)境獨立運行。
當然,這也并不意味著(zhù)任何結構性缺陷。程序的結構由你自己控制,你完全可以把需要Unit Test的那部分從Web環(huán)境脫離開(kāi)來(lái),放到Business層。
如同WebWork,Maverick直接支持所有的主流View。Maverick的配置文件采Struts, Cocoon兩家之長(cháng),URL -> Action -> View映射的主體結構類(lèi)似于Struts,而View定義部分對Transform的支持則類(lèi)似于Cocoon。如:
<command name="friends">
<controller class="org.infohazard.friendbook.ctl.Friends"/>
<view name="success" path="friends.jsp">
<transform path="trimInside.jsp"/>
</view>
</command>
http://www.springframework.com/
Spring MVC是我見(jiàn)過(guò)的結構最清晰的MVC Model 2實(shí)現。
Action不叫Action,準確地稱(chēng)做Controller;Controller接收request, response參數,干脆利落地返回ModelAndView(其中的Model不是Object類(lèi)型,而是Map類(lèi)型)。
其它的Web Framework中, Action返回值一般都只是一個(gè)View Name;Model則需要通過(guò)其它的途徑(如request.attribute,Context參數,或Action本身的屬性數據)傳遞上去。
Spring以一招IoC名滿(mǎn)天下,其AOP也方興未艾。“Spring出品,必屬精品”的觀(guān)念已經(jīng)深入人心。我這里多說(shuō)也無(wú)益,強烈建議讀者去閱讀Spring Doc & Sample & Code本身。
http://jakarta.apache.org/turbine/
Turbine是一個(gè)提供了完善權限控制的堅實(shí)框架(Fulcrum子項目是其基石)。Turbine的個(gè)人用戶(hù)不多,但不少公司用戶(hù)選擇Turbine作為框架,開(kāi)發(fā)一些嚴肅的應用(我并沒(méi)有說(shuō),用其它框架開(kāi)發(fā)的應用就不嚴肅^_^)。Portal開(kāi)源項目JetSpeed建立在Turbine上。
Turbine用RunData來(lái)傳遞輸入輸出數據。如同Maverick的ControllerContext,RunData是整個(gè)Turbine框架的數據交換中心。除了request, response等基本信息,RunData直接包括了User/ACL等權限控制相關(guān)的屬性和方法,另外還包括Action Name和Target Template Name等定位屬性。
Module是Turbine里面除了RunData之外的又一個(gè)核心類(lèi),是Turbine框架的基本構件,Action是Module,Screen也是Module。Turbine提供了LoginUser和LogoutUser兩個(gè)Action作為整個(gè)系統的出入口。而其余流量的權限控制則由類(lèi)似于Servlet Filter機制的Pipeline控制。
Turbine Pipeline的編程模型和Servlet Filter一模一樣:Turbine Pipeline的Valve就相當于Servlet Filter,而ValveContext則相當于Filter Chain。還有更相近的例子,Tomcat源代碼里面也有Valve和ValueContext兩個(gè)類(lèi),不僅編程模型一樣,而且名字也一樣。
權限控制貫穿于Turbine框架的始終。要用好Turbine,首先要通曉子項目Fulcrum 的Security部分的權限實(shí)現模型。
Fulcrum Security的權限實(shí)體包括四個(gè)-- User, Group, Role, Permission。
實(shí)體之間包含{Role,Permission}和{ Group, User, Role}兩組關(guān)系。
{Role,Permission}是多對多的關(guān)系,一個(gè)Role可以具有各種Permission;{ Group, User, Role}之間是多對多的關(guān)系,一個(gè)Group可包含多個(gè)User,并可以給User分配不同的Role。
權限模型的實(shí)現同樣采用Peer模式,Entity -> EntityPeer, Entity -> ManagerPeer。
Entity和EntityManger代表抽象的模型概念,而EntityPeer和ManagerPeer代表具體的實(shí)現。
用戶(hù)可以根據模型,提供不同的實(shí)現,比如,用內存結構中實(shí)現,用數據表結構實(shí)現,與Windows NT權限驗證機制結合,與OSWorkflow的權限控制模型結合,等等。其中,用數據表結構實(shí)現,又可以選擇用Torque實(shí)現,或者用Hibernate實(shí)現。(Torque是Turbine的O/R Mapping子項目)
例如,Falcrum.property配置文件包含如下Security相關(guān)選項:
# -------------------------------------------------------------------
# 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
這說(shuō)明,權限控制實(shí)現由數據庫提供,需要根據權限模型創(chuàng )建如下數據表:
TURBINE_USER,TURBINE_ROLE,TURBINE_GROUP,
TURBINE_PERMISSION,TURBINE_ROLE_PERMISSION,
TURBINE_USER_GROUP_ROLE。
Cocoon項目是一個(gè)叫好不叫做的框架。采用XML + XSLT Pipeline機制,Java程序只需要輸出XML數據,Cocoon框架調用XSL文件把XML數據轉換成HTML、WML等文件。
Cocoon強大靈活的XSL Pipeline配置功能,XSLT的內容/顯示分離的承諾,一直吸引了不少程序員fans。怎奈天不從人愿,由于復雜度、速度瓶頸、XSL學(xué)習難度等問(wèn)題的限制,Cocoon一直主要限于網(wǎng)站發(fā)布出版領(lǐng)域,向CMS和Portal方向不斷發(fā)展。另外,Cocoon開(kāi)發(fā)了XSP腳本和Cocoon Form技術(shù)。
Cocoon的sitemap.xmap配置文件比較復雜,與其它的Web Framework差別很大。
主體Pipelines配置部分采用Pattern Match的方式,很像XSL語(yǔ)法,也可以類(lèi)比于Web.xml里面Servlet Mapping的定義。比如,一個(gè)典型的URL->Action的映射定義看起來(lái)是這個(gè)樣子:
<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>
http://barracudamvc.org/Barracuda/index.html
Barracuda是一個(gè)HTML DOM Component + Event/Listener結構的框架。
根據模板文件或配置文件生成靜態(tài)Java類(lèi),并在代碼中使用這些生成類(lèi),是Barracuda的一大特色。
Barracuda需要用XMLC項目把所有的HTML或WML模板文件,靜態(tài)編譯成DOM結構的Java類(lèi),作為頁(yè)面組件。XMLC會(huì )根據HTML元素的id定義,生成相應DOM結點(diǎn)的簡(jiǎn)便操作方法。
Barracuda的事件類(lèi)也需要用Barracuda Event Builder工具把event.xml編譯成Java類(lèi),引入到工程中。Barracuda直接用Java類(lèi)的繼承關(guān)系映射事件之間的父子層次關(guān)系。比如,ChildEvent是ParentEvent的子類(lèi)。
Barracuda的事件分為兩類(lèi):Request Events(Control Events)和Response Events(View Events)。
Barracuda事件處理過(guò)程很像Windows系統消息隊列的處理機制。
(1) Barracuda根據HTTP Request生成Request Event,放入到事件隊列中。
(2) EventDispatcher檢查事件隊列是否為空,如果為空,結束。如果非空,按照先進(jìn)先出的方式,從事件隊列中取出一個(gè)事件,根據這個(gè)事件的類(lèi)型,選擇并調用最合適的EventListener,參數Event Context包含事件隊列。
“根據事件類(lèi)型,選擇最合適的EventListener對象”的過(guò)程是這樣的:比如,
EventDispatcher從時(shí)間隊列里取出來(lái)一個(gè)事件,類(lèi)型是ChildEvent;Barracuda首先尋找注冊了監聽(tīng)ChildEvent的EventListener,如果找不到,再上溯到ChildEvent的父類(lèi)ParentEvent,看哪些EventListener對ParentEvent感興趣。
詳細過(guò)程參見(jiàn)Barracuda的DefaultEventDispatcher類(lèi)。
(3) EventListener根據Event Context包含的request信息,調用商業(yè)邏輯,獲得結果數據,然后根據不同情況,把新的事件加入到Event Context的事件隊列中。
(4) 控制交還給EventDispatcher,回到第(2)步。
The End.
Enjoy.
聯(lián)系客服