Spring內建其他Web架構
- 雖然Spring本身也提供了一個功能非常強大的MVC架構,并且和Spring的IoC容器無縫內建,非常便于使用。不過,在實際情況中,我們還不得不考慮衆多采用第三方MVC架構的Web應用程式,例如,采用Strtus、WebWork等。如果要将這些應用程式的邏輯元件和持久化元件納入Spring的IoC容器中進行管理,就必須考慮如何內建這些第三方的MVC架構。Spring強大的內建和擴充能力使得內建第三方MVC架構成為輕而易舉的事情。
- 與第三方MVC架構內建時,需要考慮的問題是如何在第三方MVC架構中通路Spring IoC容器管理的Bean,或者說,如何擷取Spring IoC容器的執行個體。我們還要保證在第三方MVC架構開始處理使用者請求之前,Spring的IoC容器必須初始化完畢。
- 有兩種方式可以自動加載Spring的IoC 容器。一種是利用第三方架構的擴充點,實作加載Spring的IoC容器,例如,Struts就提供了Plugin擴充。第二種方式是在web.xml中定義Listener或Servlet,讓Web應用程式一啟動就自動加載Spring的IoC容器。這種方式較為通用,對于一些不太常見的第三方MVC 架構,也可以用這種方式來嘗試與Spring內建。
- 如果正在使用基于Servlet 2.3或更高規範的Web伺服器,則應當使用Spring提供的ContextLoaderListener來加載IoC容器,因為根據Servlet 2.3規範,所有的Listener都會在Servlet被初始化之前完成初始化。由于我們希望盡可能早地完成Spring IoC容器的初始化,是以,采用ContextLoaderListener加載Spring的IoC容器是最合适的。
- 采用ContextLoaderListener加載Spring的IoC容器時,在/WEB-INF/web.xml中定義如下。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" " http://java.sun.com/dtd/web-app_2_3.dtd">
- <web-app>
- ...
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
- ...
- </web-app>
- 預設地,ContextLoaderListener會在/WEB-INF/目錄下查找名為applicationContext. xml檔案,作為Spring的XML配置檔案加載。如果使用其他檔案名,或者有多個XML配置檔案,就需要預先在<context- param>中指定。
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/cfg1.xml,/WEB-INF/cfg2.xml</param-value>
- </context-param>
- 如果使用的Web伺服器還不支援Servlet 2.3規範,則無法使用Listener,也就無法通過ContextLoaderListener來加載Spring的IoC容器。為此,Spring 提供了另一個ContextLoaderServlet,以Servlet的形式來加載Spring的IoC容器。
- <servlet>
- <servlet-name>contextLoader</servlet-name>
- <servlet-class>
- org.springframework.web.context.ContextLoaderServlet
- </servlet-class>
- <load-on-startup>0</load-on-startup>
- </servlet>
- ContextLoaderServlet 查找Spring的XML配置檔案的方式與ContextLoaderListener完全一緻,不過,由于必須首先加載 ContextLoaderServlet,然後加載其他的Servlet,才能保證Spring的IoC容器在其他Servlet處理使用者請求之前初始化完畢。是以,設定<load-on-startup>為0,表示Web伺服器一啟動就加載ContextLoaderServlet,對于其他的Servlet,<load-on-startup>的值要設得大一些,保證ContextLoaderServlet有足夠的時間初始化Spring的IoC容器。
- 一旦完成了Spring IoC容器的加載,另一個問題是如何在第三方應用程式中獲得Spring IoC容器的執行個體?
- 事實上,不管采用何種方式加載Spring的 IoC容器,Spring最終都會将IoC容器的執行個體綁定到ServletContext上。由于ServletContext在一個Web應用程式中是全局唯一的,是以該Web應用程式中所有的Servlet都可以通路到這個唯一的ServletContext,也就可以獲得Spring IoC容器的執行個體。Spring提供了一個輔助類WebApplicationContextUtils,通過調用 getWebApplicationContext(ServletContext)方法就可以獲得Spring IoC容器的執行個體。對于參數ServletContext,可以在Servlet中随時調用getServletContext()獲得。
- 一旦獲得了Spring IoC容器的執行個體,就可以獲得Spring管理的所有的Bean元件。
-
下面,我們分别詳細介紹如何在Spring中內建Struts、WebWork2、Tiles和JSF。
7.6.1 內建Struts
- Struts是目前JavaEE Web應用程式中應用最廣泛的MVC開源架構,自從2001年釋出以來,Struts作為JavaEE領域的第一個MVC架構,極大地簡化了基于JSP和 Servlet的Web開發,提供了統一的MVC程式設計模型。雖然從現在看來,Struts的設計模型已不算先進,有許多其他MVC架構擁有更好的設計,但 Struts仍具有龐大的社群支援和最多的開發人員,這些都使得Struts仍是JavaEE領域内Web開發的首選架構。
- 不過,Strtus僅僅是一個用于表示層的MVC架構,它并沒有提供一個完整的JavaEE架構的解決方案。如果要将Struts內建到Spring架構中,有兩種方案可供選擇。下面,我們将詳細講解如何将Struts內建到Spring架構中。
- 在內建Struts之前,我們假定已經有了一個基于Struts的Web應用程式,這裡我們使用的例子是一個簡單的處理使用者登入的Struts應用,這個Struts應用在浏覽器中的效果如圖7-54所示。
- 圖7-54
- 在Eclipse中,我們建立了這個名為Struts的工程,其結構如圖7-55所示。
- 圖7-55
- 在Struts 應用中,每個使用者請求通過一個Action類來處理,這和Spring的Controller類似,但是Struts的Action是一個類而非接口,是以,所有的Action子類都隻能從Action派生。由于Struts是一個Web層架構,需要考慮如何獲得業務邏輯接口。一個較好的設計是首先設計一個BaseAction,其中定義了獲得業務邏輯接口的方法,其他所有的Action從BaseAction派生即可非常友善地調用業務邏輯接口。
- public class BaseAction extends Action {
- private static final UserService userService = new UserServiceImpl();
- public UserService getUserService() {
- return userService;
- }
- }
- 在BaseAction 中,我們以靜态變量持有業務邏輯接口UserService的引用,這是一個不太優雅的設計。如果使用Spring的IoC容器來配置和管理這些邏輯元件,則可以完全實作一個優雅的多層應用程式,Struts隻處理Web表示層,業務邏輯層和資料持久層都交由Spring管理,便于維護和擴充。
- 在Spring中有兩種方式來內建 Struts,關于Struts的更多詳細用法的讨論已經超出了本書的範圍。在本節中,假定讀者對Struts已經有了一定的基礎。下面我們分别介紹 Spring內建Struts的兩種方案,兩種方案都需要Spring提供的一個名為ContextLoader Plugin的Struts插件來啟動IoC容器。由于是Web應用程式,ContextLoaderPlugin啟動的是 WebApplicationContext的執行個體。
- 第一種方案是通過Spring提供的一個Struts插件來初始化IoC容器,然後從Spring提供的ActionSupport派生所有的 Action,以便能通過getWebApplicationContext()獲得ApplicationContext,一旦獲得了 ApplicationContext引用,就可以獲得Spring的IoC容器中所有的Bean。我們建立一個Struts_Spring1工程,首先複制Struts工程的所有檔案,然後在Struts配置檔案的最後添加Spring的插件聲明。
- <struts-config>
- ...
- <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn" />
- </struts-config>
- 然後修改BaseAction,将其超類從Struts的Action改為Spring的ActionSupport。
- public class BaseAction extends ActionSupport {
- public UserService getUserService() {
- return (UserService) getWebApplicationContext()
- .getBean("userService");
- }
- }
- 現在,BaseAction就可以随時通過Spring的ApplicationContext獲得邏輯元件UserService的引用,這樣所有的Action子類都可以直接通過getUserService()獲得UserService元件。
- 最後一步是編寫Spring的XML配置檔案,預設的檔案名是<servlet-name> -servlet.xml,由于我們在web.xml中配置Struts的ActionServlet名稱為action,是以,Spring的配置檔案為action-servlet.xml,放到web/WEB-INF/目錄下,定義所有的Bean元件,但不包括Struts的Action執行個體。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns=" http://www.springframework.org/schema/beans"
- xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
- <bean id="userService" class="example.chapter7.UserServiceImpl" />
- </beans>
- 如果Spring的XML配置檔案沒有使用預設的檔案名,就必須在Struts配置檔案中聲明Plugin時指定檔案位置。
- <struts-config>
- ...
- <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
- <set-property property="contextConfigLocation"
- value="/WEB-INF/my-spring-config.xml"/>
- </plug-in>
- </struts-config>
- 整個Struts_Spring1工程的結構如圖7-56所示。
- 圖7-56
- 編譯工程,然後啟動Resin伺服器,可以看到效果和原始的Struts工程一樣,但是,業務邏輯層元件和持久層元件(在本例中,為了簡化問題,沒有設計持久層元件)已被納入Spring的IoC容器之中,進而清晰地将表示層和業務層劃分開來。
- 使用這種方式內建Spring和Struts 時,如果合理地抽象出一個類似BaseAction的類作為所有Action的超類,在內建到Spring時,隻需将BaseAction的超類從 Struts的Action類改為Spring提供的ActionSupport,然後在Struts中聲明Spring的Plugin,就可以在不修改任何Action的情況下實作和Spring的內建。
- 第二種內建Struts的方案是将 Struts的所有Action都作為Spring IoC容器中的Bean來管理,就像Spring的Controller一樣。然後通過Spring提供的 DelegatingRequestProcessor來替代Struts的預設派發器,以便讓Spring能截獲派發給Action的請求。在這種方式下,業務邏輯元件通過依賴注入的方式在Spring的IoC容器中就配置完畢了。
- 我們仍建立一個Struts_Spring2的工程,将Struts工程的所有檔案複制過來,其目錄結構和Struts_Spring1類似,如圖7-57所示。
- 圖7-57
- 為了能将業務元件UserService注入到每個Action類中,抽象出一個BaseAction是非常重要的,這樣可以使代碼的修改被限制在BaseAction中,而不會涉及每一個Action類。修改BaseAction如下。
- public class BaseAction extends Action {
- private UserService userService;
- public void setUserService(UserService userService) {
- this.userService = userService;
- }
- public UserService getUserService() {
- return userService;
- }
- }
- 然後修改Struts的配置檔案,添加Spring提供的DelegatingRequestProcessor和Plugin的聲明。
- <struts-config>
- ...
- <controller processorClass="org.springframework.web.struts.Delegating RequestProcessor"/>
- ...
- <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn" />
- </struts-config>
- Spring 提供的Struts Plugin仍負責啟動Spring容器,而DelegatingRequestProcessor可以讓Spring接管請求,進而将請求派發到IoC 容器管理的Action執行個體。為此,在Spring的XML配置檔案中,除了定義業務邏輯元件和其他元件外,還必須聲明每一個Action類,其name 屬性和Struts配置檔案中的path要完全一緻。由于我們在Struts配置檔案中定義了兩個Action:
- <action-mappings>
- <action path="/login" type="example.chapter7.struts.LoginAction" ... />
- <action path="/logout" type="example.chapter7.struts.LogoutAction" ... />
- </action-mappings>
- 是以,在Spring的配置檔案中,除UserService元件外,還必須定義兩個Action,并且要和Struts配置檔案中的Action一一對應。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns=" http://www.springframework.org/schema/beans"
- xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
- <bean id="userService" class="example.chapter7.UserServiceImpl" />
- <bean name="/login" class="example.chapter7.struts.LoginAction">
- <property name="userService" ref="userService" />
- </bean>
- <bean name="/logout" class="example.chapter7.struts.LogoutAction">
- <property name="userService" ref="userService" />
- </bean>
- </beans>
- 實際上,如果使用這種方案實作內建,Struts配置檔案中Action的type屬性将被忽略,因為Spring會根據path屬性查找對應name的Action Bean。但是,仍然建議将type屬性辨別出來,便于檢視URL和Action的關聯。
- 使用這種方案時,由于Action被作為Bean納入Spring的IoC容器管理,是以,可以獲得完全的依賴注入能力。不過,最大的不便是所有的Action必須同時在Struts和Spring的配置檔案中各配置一次,這增加了配置檔案的維護成本。
-
在內建Struts時,選擇何種方案需要根據實際情況決定。例如,由于RequestProcessor是Struts的一個擴充點,如果現有的Web應用程式已經擴充了 RequestProcessor,采用第一種方式就比較合适。不過,無論采用哪種方案,基于Struts的整體設計至關重要,如果沒有統一的業務邏輯接口,而是将業務邏輯散落在各個Action中,則對代碼的修改将涉及所有的Action類。
7.6.2 內建WebWork2
- WebWork是一個非常簡潔和優雅的Web 架構,WebWork的架構設計非常容易了解,它建構在一個指令模式的XWork架構之上,支援多種視圖技術,并且WebWork也有一個豐富的标簽庫,能非常容易地實作校驗。WebWork最大的特色就是使用IoC容器來管理所有的Action,并且擁有攔截器等一系列類似AOP的概念,是以, WebWork的整體設計比Struts更優秀,也更容易擴充。
- WebWork目前有兩個主要版本: WebWork 1.x和WebWork 2.x,兩個版本的差異較大,本節介紹的均以最新的WebWork 2.2為例。可以從WebWork的官方站點 http://www. opensymphony.com/webwork/下載下傳最新的2.2.4版。
- 在WebWork 2.2版本之前,WebWork有一個自己的IoC容器,盡管仍可以和Spring內建,但是不那麼友善。從WebWork 2.2開始,WebWork的設計者就推薦使用Spring的IoC容器來管理WebWork2的Action,這對于使用Spring架構的開發者來說無疑是一個好消息,因為這使得兩者的內建更加容易,我們可以像對待Spring的Bean一樣,在Spring的IoC容器中直接配置WebWork2的 Action。
- WebWork2的開發團隊已經提供了一個 Spring和WebWork2的內建方案,正因為如此,在Spring架構中并沒有WebWork2相關的支援包。如果讀者在內建WebWork2時遇到問題,請首先參考WebWork官方網站( http://www.opensymphony.com/webwork/)的相關文檔。
- 如果讀者正在考慮使用WebWork2作為 MVC架構,則理所當然應當內建到Spring架構中。在下面的例子中,我們以最新的WebWork 2.2.4為例,首先建立一個基于WebWork2的Web應用程式,命名為WebWork2,其Eclipse工程結構如圖7-58所示。
- WebWork2所需的jar檔案除了 webwork-2.2.4.jar外,其餘必需的jar包可以從解壓後的WebWork2的lib/default/目錄下找到,将這些jar包(javamail.jar可選)全部複制到/WEB-INF/lib目錄下。其中,xwork.jar是編譯時必須的,将其添加到Eclipse的 Build Path中。由于WebWork2架構是基于XWork架構之上的,對開發者而言,開發WebWork2的Action甚至不用和Servlet API打交道,這種設計大大提高了Web應用程式的可測試性。
- 圖7-58
- 我們假定讀者對WebWork2已經有了一定的了解。在這個Web應用程式中,我們定義了兩個Action,ListBooksAction用于列出所有的書籍,BookDetailAction用于檢視書籍的詳細資訊,為此,我們還定義了一個BookService接口,并在所有Action的超類 BaseAction中定義了擷取BookService接口的方法getBookService()。
- public abstract class BaseAction implements Action {
- private static BookService bookService = new BookServiceImpl();
- public BookService getBookService() {
- return bookService;
- }
- }
- 然後,在xwork.xml中配置好兩個Action,并放到src目錄下,這樣,編譯後該檔案就位于/WEB-INF/classes目錄下。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE xwork
- PUBLIC
- "-//OpenSymphony Group//XWork 1.0//EN"
- " http://www.opensymphony.com/xwork/xwork-1.0.dtd">
- <xwork>
- <include file="webwork-default.xml"/>
- <package name="default" extends="webwork-default">
- <action name="listBooks" class="example.chapter7.webwork2.ListBooksAction">
- <result name="success" type="dispatcher">/listBooks.jsp</result>
- </action>
- <action name="bookDetail" class="example.chapter7.webwork2. BookDetailAction">
- <result name="success" type="dispatcher">/bookDetail.jsp</result>
- <result name="error" type="dispatcher">/notFound.jsp</result>
- </action>
- </package>
- </xwork>
- 在web.xml中,我們聲明WebWork2的ServletDispatcher,并映射所有以.action結尾的URL。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" " http://java.sun.com/dtd/web-app_2_3.dtd">
- <web-app>
- <servlet>
- <servlet-name>dispatcher</servlet-name>
- <servlet-class>
- com.opensymphony.webwork.dispatcher.ServletDispatcher
- </servlet-class>
- <load-on-startup>0</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>dispatcher</servlet-name>
- <url-pattern>*.action</url-pattern>
- </servlet-mapping>
- <taglib>
- <taglib-uri>webwork</taglib-uri>
- <taglib-location>/WEB-INF/lib/webwork-2.2.4.jar</taglib-location>
- </taglib>
- </web-app>
- 現在,作為一個獨立的WebWork2的應用程式,我們已經可以在浏覽器中看到實際效果了。啟動Resin伺服器,然後輸入位址“http://localhost:8080/listBooks.action”,如圖7-59所示。
- 我們還沒有将Spring內建進來,并且和7.6.1節的Struts應用程式類似,擷取BookService接口設計得不那麼優雅。下一步,我們将WebWork2與Spring內建起來,讓Spring來管理所有的Action和其他邏輯元件。
- 圖7-59
- 我們将WebWork2工程複制一份,命名為WebWork2_Spring,然後開始一步一步地将其內建到Spring架構中。
- 在WebWork2中內建Spring是一件非常容易的事情。在WebWork2中,所有的Action也是由IoC容器管理的、預設地,WebWork2自身有一個IoC容器,負責管理所有的 Action,但是,正如前面提到的,從WebWork 2.2開始,WebWork2設計者推薦使用Spring的IoC容器來管理Action。隻要對配置檔案稍做修改,就可以讓WebWork2直接使用 Spring的IoC容器。
- 第一步是編寫一個webwork.properties的配置檔案,放在src目錄下,指定WebWork2使用的IoC容器名稱。
- webwork.objectFactory = spring
- 該配置檔案非常簡單,隻需要以上一行内容即可。更多的配置選項可以參考WebWork2的文檔。編譯後,該檔案就會被放到/WEB-INF/classes/目錄下。
- 現在,隻要Spring的IoC容器啟動了,WebWork2就會自動感覺并使用Spring的IoC容器。要啟動Spring的IoC容器,使用 ContextLoaderListener最合适不過,在web.xml中添加Spring的ContextLoaderListener聲明。
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
- 下一步是在Spring預設的XML配置檔案中定義所有的Bean,包括WebWork2的Action。由于使用 ContextLoaderListener來啟動Spring的IoC容器,是以,預設的XML配置檔案名稱為 applicationContext.xml,内容如下。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns=" http://www.springframework.org/schema/beans"
- xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
- >
- <bean id="bookService" class="example.chapter7.BookServiceImpl" />
- <bean id="listBooksAction" class="example.chapter7.webwork2. ListBooks Action" scope="prototype">
- <property name="bookService" ref="bookService" />
- </bean>
- <bean id="bookDetailAction" class="example.chapter7.webwork2. BookDetail Action" scope="prototype">
- <property name="bookService" ref="bookService" />
- </bean>
- </beans>
- 要特别注意的是,WebWork2使用的Action和Struts不同,對于每個使用者請求,WebWork2都會建立一個新的Action執行個體來處理使用者請求,是以,必須将Action的scope定義為prototype,使得每次WebWork2向Spring IoC容器請求一個Action時,Spring都能傳回一個新的執行個體。采用Bean模闆也不失為一個好辦法,具體配置請參考第3章3.9.2“使用模闆裝配”一節。
- 最後一步是修改xwork.xml,将每個Action的class屬性從類名改為Spring中對應的id,這樣,WebWork2就會根據此id向Spring的IoC容器請求一個新的Action執行個體。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE xwork
- PUBLIC
- "-//OpenSymphony Group//XWork 1.0//EN"
- " http://www.opensymphony.com/xwork/xwork-1.0.dtd">
- <xwork>
- <include file="webwork-default.xml"/>
- <package name="default" extends="webwork-default">
- <action name="listBooks" class="listBooksAction">
- <result name="success" type="dispatcher">/listBooks.jsp</result>
- </action>
- <action name="bookDetail" class="bookDetailAction">
- <result name="success" type="dispatcher">/bookDetail.jsp</result>
- <result name="error" type="dispatcher">/notFound.jsp</result>
- </action>
- </package>
- </xwork>
- 完成了以上步驟後,将spring.jar放入/WEB-INF/lib目錄下,然後啟動Resion伺服器,就可以看到如下輸出。
- [2006/12/06 16:41:43.138] Initializing WebWork-Spring integration...
- [2006/12/06 16:41:43.154] Setting autowire strategy to name
- [2006/12/06 16:41:43.154] ... initialized WebWork-Spring integration successfully
- 打開浏覽器測試,其結果與前面WebWork2工程的結果完全相同,不過,所有的Action和邏輯元件都由Spring的IoC來管理,這使得應用程式的層次更加清晰。WebWork2_Spring工程的整個結構如圖7-60所示。
- 圖7-60
- 預設情況下,在使用Spring的IoC容器時,WebWork2使用byName的自動裝配功能來裝配WebWork2的Action。可以根據需要将其修改為byType。如果Action較多,更好的配置方法是首先定義一個模闆Bean,作為所有Action Bean的模闆。
- <bean id="webworkActionTemplate" abstract="true" scope="prototype">
- <property name="service1" ref="service1" />
- <property name="service2" ref="service2" />
- </bean>
-
其餘的Action Bean通過繼承此模闆Bean就可以安全地獲得scope設定和其他依賴注入的屬性。
7.6.3 內建Tiles
- 前面我們講到了JavaEE Web元件處理請求的3種方式:轉發(Forward)、包含(Include)和錯誤(Error),Tiles架構将Include方式發揮到了極緻。Tiles并不是一個MVC架構,從本質上說,Tiles是一個模闆架構,它将頁面分成幾個小的部分,然後動态地将它們組合在一起,進而允許更靈活地建立可以重用的頁面元件。
- 雖然Tiles是Struts架構的一部分,但是Tiles從一開始就被設計為能夠在Struts外獨立适用。事實上,Tiles可以用于任何Web架構,當然也包括Spring的MVC架構。
- Spring已經對Tiles做了非常好的內建,在Spring架構中使用Tiles更加友善。下面的例子我們建立了一個Spring_Tiles工程,結構如圖7-61所示。
- 圖7-61
- 将Tiles 所有依賴的jar包放入工程,包括struts.jar、commons-beanutils.jar、commons- collections.jar、commons-digester.jar、commons-logging.jar和spring.jar。注意, Tiles架構本身被包含在struts.jar中,但是我們并不使用Struts作為MVC架構。然後配置web.xml,除了聲明Spring的 DispatcherServlet外,還要聲明Tiles的taglib。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE web-app PUBLIC
- "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- " http://java.sun.com/dtd/web-app_2_3.dtd">
- <web-app>
- <servlet>
- <servlet-name>dispatcher</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</ servlet-class>
- <load-on-startup>0</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>dispatcher</servlet-name>
- <url-pattern>*.do</url-pattern>
- </servlet-mapping>
- <taglib>
- <taglib-uri>http://struts.apache.org/tags-tiles</taglib-uri>
- <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
- </taglib>
- </web-app>
- 我們先來看看如何在Tiles中組合出一個頁面。在這個Web應用程式中,我們一共設計了兩個頁面,一個是登入頁面,一個是歡迎頁面,每個頁面都被分為頁眉(header)、主體(body)和頁腳(footer)三部分。為了能在Tiles中組合出一個完整的頁面,我們分别編寫header.jsp作為頁眉,footer.jsp作為頁腳,在登入頁中,主體部分是login.jsp,而在歡迎頁中,主體是welcome.jsp,如圖7-62所示。
- 通過将頁面拆為幾個部分,我們就可以複用頁面的公共部分(如header.jsp和footer.jsp),在Tiles中,每個可重用的部分被稱為一個可視化元件,通常是一個JSP檔案,然後用一個模闆将幾個元件組合到一起,就構成了一個完整的頁面。例如,template.jsp将頁眉、主體和頁腳3個元件組合在一起,作為一個完整的頁面展示給使用者。
- <%@ page contentType="text/html; charset=utf-8" %>
- <%@ taglib prefix="tiles" uri=" http://struts.apache.org/tags-tiles" %>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title><tiles:getAsString name="title" /></title>
- </head>
- <body>
- <table width="100%" cellspacing="0" cellpadding="0">
- <tr><td><tiles:insert name="header" /></td></tr>
- <tr><td><tiles:insert name="body" /></td></tr>
- <tr><td><tiles:insert name="footer" /></td></tr>
- </table>
- </body>
- </html>
- 組合tile非常直覺,用<tiles:insert>标簽就可以嵌入一個tile,用<tiles:getAsString>可以将一個屬性寫到頁面中。在template.jsp模闆裡,一共插入了3個元件和1個屬性值作為标題。
- 在template.jsp 中,我們隻定義了每個元件的名稱,并沒有指定具體的jsp檔案。在Tiles中,每個頁面的布局都被定義在XML配置檔案中,我們将Tiles的配置檔案命名為tiles-defs.xml,并放到/web/WEB-INF/目錄下,在這個配置檔案中,我們定義了login和welcome兩個完整的頁面。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE tiles-definitions PUBLIC
- "-//Apache Software Foundation//DTD Tiles Configuration 1.3//EN"
- " http://struts.apache.org/dtds/tiles-config_1_3.dtd">
- <tiles-definitions>
- <definition name="default" path="/template.jsp">
- <put name="title" value="Default Title" />
- <put name="header" value="/header.jsp" />
- <put name="footer" value="/footer.jsp" />
- </definition>
- <definition name="login" extends="default" >
- <put name="title" value="Please login" />
- <put name="body" value="/login.jsp" />
- </definition>
- <definition name="welcome" extends="default">
- <put name="title" value="Welcome!" />
- <put name="body" value="/welcome.jsp" />
- </definition>
- </tiles-definitions>
- 每個 <definition>定義一個完整的頁面,在Tiles中,頁面是可以繼承的,例如,上述default頁面定義的header和 footer分别為header.jsp和footer.jsp,并設定頁面的title屬性為“Default Title”,login頁面就可以從default繼承,并定義body為login.jsp,然後覆寫了title屬性。通過繼承,就使得頁面定義更加簡潔。
- 最後一步是在Spring的XML配置檔案中配置Tiles架構,并選擇一個合适的ViewResolver,讓它能解析Tiles視圖。
- <bean id="tilesConfig" class="org.springframework.web.servlet.view. tiles.TilesConfigurer">
- <property name="definitions">
- <list>
- <value>/WEB-INF/tiles-defs.xml</value>
- </list>
- </property>
- </bean>
- <bean id="viewResolver" class="org.springframework.web.servlet.view. InternalResourceViewResolver">
- <property name="viewClass" value="org.springframework.web.servlet.view. tiles.TilesView" />
- </bean>
- TilesConfigurer隻需要指定Tiles配置檔案就可以自動地配置好Tiles,為了讓視圖解析器能識别TilesView,使用InternalResourceViewResolver并将TilesView綁定即可。
- LoginController和 WelcomeController的編寫和前面的完全相同,在LoginController中,傳回的視圖名稱是“login”,而 WelcomeController傳回的視圖名稱是“welcome”,細心的讀者可能已經發現了,視圖解析器的任務就是在Tiles配置檔案中查找對應名稱的頁面,然後根據頁面的定義,由TilesView将整個頁面渲染出來。兩個頁面的效果如圖7-63和圖7-64所示。
-
圖7-63 圖7-64
7.6.4 內建JSF
- JSF是JavaServer Faces的縮寫,JSF是JavaEE中建構Web應用程式的又一個全新的MVC架構。與其他常見的MVC架構相比,JSF提供了一種以元件為中心的方式來開發Web應用程式。JSF的目标是提供一種類似ASP.Net的可視化Web元件,開發人員隻需要将已有的JSF元件拖放到頁面上,就可以迅速建立一個Web應用程式,而不必從頭開始編寫UI界面。是以,JSF的易用性很大程度上取決于一個簡單、高效的可視化開發環境。
- 和傳統的MVC架構(如Struts)相比, JSF更像是一個Web版本的Swing應用程式。傳統的MVC架構的View是以頁面為中心的,而JSF的View則是一系列可複用的UI元件。是以, JSF應用程式的生命周期更為複雜。一般來說,JSF處理使用者請求的流程如圖7-65所示。
- 除了恢複視圖和渲染響應是必須的之外,其他步驟都可以視情況跳過。例如,在驗證失敗後,就直接跳到渲染響應,将錯誤資訊展示給使用者。
- JSF的真正威力在于它的UI界面元件。與 ASP.NET類似,JSF的UI元件使開發人員能夠使用已建立好的UI元件(如JSF内置的UI元件)來建立Web頁面,而非完全從頭建立,進而提供了前所未有的開發效率。JSF的UI元件可以是簡單到隻是顯示文本的outputLabel,或者複雜到可以表示來自資料庫表的表格。
- 此外,JSF也使用類似Spring IoC容器的方式來管理Model,即與UI元件綁定的Bean,在JSF中稱為Managed-Bean,并且也支援依賴注入。Managed- Bean的生命周期可以是request、session和application,分别對應一次請求、一次會話和整個Web應用程式生存期。
- 在開發JSF應用之前,我們需要首先準備開發環境。由于JSF也是一個規範,不同的廠商都可以有自己的實作。這裡我們采用的是JSF 1.1規範(JSR 127),因為JSF 1.1僅需要Servlet 2.3規範的支援,在大多數Web伺服器上都能正常運作,而最新的JSF 1.2則要求Servlet 2.5規範,目前除了少數伺服器外,大多數伺服器還無法支援。
- 我們還需要一個JSF 1.1的實作。SUN給出了一個JSF 1.1的參考實作,可以用于開發,以保證我們的JSF應用程式将來可以移植到其他的JSF實作。Apache也提供了一個MyFaces的JSF實作,讀者可以參考Apache的官方站點擷取詳細資訊,本書不對此做更多讨論。
- 下面的例子改自IBM developerWorks站點的一個JSF應用,它允許使用者輸入自己的姓名和電子郵件位址來訂閱新聞。從http: //java.sun.com/javaee/javaserverfaces/download. html下載下傳JSF 1.1的參考實作并解壓,然後在Eclipse中建立一個Spring_JSF工程,首先實作一個基于JSF的Web應用程式,将相關jar包放入 /WEB-INF/lib目錄,工程結構如圖7-66所示。
- 圖7-66
- 編譯工程隻需要引用jsf-api.jar。幾個主要的接口和類如下。
- Service接口是唯一的業務邏輯接口,它僅定義了一個subscribe方法。
- public interface Service {
- void subscribe(SubscriberBean subscriber);
- }
- 其實作類ServiceImpl僅僅簡單地列印出使用者的訂閱資訊。
- public class ServiceImpl implements Service {
- public void subscribe(SubscriberBean subscriber) {
- System.out.println("User /"" + subscriber.getName()
- + "/" with email /"" + subscriber.getEmail()
- + "/" subscribed successfully with preferred language "
- + subscriber.getLanguage());
- }
- }
- SubscriberBean 是一個與前端UI綁定的Session範圍的Managed-Bean,其作用範圍是Session,在SubscriberBean中還定義了 submit()方法來處理JSF的Action,是以,在SubscriberBean中必須要注入一個Service對象,才能完成實際業務方法的調用。
- public class SubscriberBean {
- private String name;
- private String email;
- private String language;
- private Service service = new ServiceImpl();
- public void setService(Service service) {
- this.service = service;
- }
- public String getName() { return name; }
- public void setName(String name) { this.name = name; }
- public String getEmail() { return email; }
- public void setEmail(String email) { this.email = email; }
- public String getLanguage() { return language; }
- public void setLanguage(String language) { this.language = language; }
- public String submit() {
- // 處理訂閱請求:
- service.subscribe(this);
- return "success";
- }
- }
- EmailValidator是一個自定義的JSF驗證器,讀者可以參考JSF相關文檔獲得有關驗證器的使用方法,這裡僅給出實作。
- public class EmailValidator implements Validator {
- public void validate(FacesContext context, UIComponent component, Object value)
- throws ValidatorException {
- String email = ((String)value).trim();
- if(!email.matches("[a-zA-Z0-9][//w//.//-]*@[a-zA-Z0-9][//w//.//-] *//.[a-zA-Z][a-zA-Z//.]*")) {
- throw new ValidatorException(
- new FacesMessage("Invalid email address"));
- }
- }
- }
- 下面我們需要配置JSF,首先,在web.xml中聲明JSF相關Listener和FacesServlet。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE web-app PUBLIC
- "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- " http://java.sun.com/dtd/web-app_2_3.dtd">
- <web-app>
- <context-param>
- <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
- <param-value>client</param-value>
- </context-param>
- <context-param>
- <param-name>com.sun.faces.validateXml</param-name>
- <param-value>true</param-value>
- </context-param>
- <listener>
- <listener-class>
- com.sun.faces.config.ConfigureListener
- </listener-class>
- </listener>
- <!-- Faces Servlet -->
- <servlet>
- <servlet-name>Faces Servlet</servlet-name>
- <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
- <load-on-startup>0</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>Faces Servlet</servlet-name>
- <url-pattern>*.faces</url-pattern>
- </servlet-mapping>
- </web-app>
- FacesServlet 是整個JSF應用程式的前端入口,負責接收所有的使用者請求。然後,需要編寫一個預設的faces-config.xml配置檔案,告訴JSF所有的配置資訊,包括自定義的驗證器、導航規則、所有的Managed-Bean和這些Bean之間的依賴關系。将faces-config.xml放到/WEB- INF/目錄下。
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE faces-config PUBLIC
- "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
- " http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
- <faces-config>
- <!-- 聲明自定義的Validator -->
- <validator>
- <validator-id>emailValidator</validator-id>
- <validator-class>example.chapter7.EmailValidator</validator-class>
- </validator>
- <!-- 聲明所有的Managed Bean -->
- <managed-bean>
- <managed-bean-name>service</managed-bean-name>
- <managed-bean-class>example.chapter7.ServiceImpl</managed-bean-class>
- <managed-bean-scope>application</managed-bean-scope>
- </managed-bean>
- <managed-bean>
- <managed-bean-name>subscriber</managed-bean-name>
- <managed-bean-class>example.chapter7.SubscriberBean</managed-bean-class>
- <managed-bean-scope>session</managed-bean-scope>
- <managed-property>
- <property-name>service</property-name>
- <value>#{service}</value>
- </managed-property>
- </managed-bean>
- <!-- 定義導航規則 -->
- <navigation-rule>
- <from-view-id>/index.jsp</from-view-id>
- <navigation-case>
- <from-outcome>success</from-outcome>
- <to-view-id>/thanks.jsp</to-view-id>
- </navigation-case>
- </navigation-rule>
- </faces-config>
- 下一步是編寫兩個JSP頁面,使用JSF标準的标簽庫,讀者可以發現JSF使用的表達式和Velocity非常類似,不過将“$”改為了“#”。index.jsp負責接受使用者輸入并驗證表單。
- < %@page contentType="text/html;charset=UTF-8" %>
- < %@taglib uri=" http://java.sun.com/jsf/core" prefix="f" %>
- < %@taglib uri=" http://java.sun.com/jsf/html" prefix="h" %>
- <html>
- <head>
- <title>Subscribe Form</title>
- </head>
- <body>
- <f:view>
- <h:form>
- <h4>Subscribe</h4>
- Your name:
- <h:inputText id="id_name" value="#{subscriber.name}" required="true">
- <f:validateLength minimum="3" maximum="20" />
- </h:inputText>
- <h:message for="id_name" />
- <br/>
- Your email:
- <h:inputText id="id_email" value="#{subscriber.email}" required="true">
- <f:validator validatorId="emailValidator" />
- </h:inputText>
- <h:message for="id_email" />
- <br/>
- Preferred Language:
- <h:selectOneMenu value="#{subscriber.language}" required="true">
- <f:selectItem itemLabel="English" itemValue="English" />
- <f:selectItem itemLabel="Chinese" itemValue="Chinese" />
- </h:selectOneMenu>
- <br/>
- <h:commandButton type="submit" value="Submit" action="#{subscriber. submit}" />
- </h:form>
- </f:view>
- </body>
- </html>
- thanks.jsp用于提示使用者訂閱成功。
- < %@page contentType="text/html;charset=UTF-8" %>
- < %@taglib uri=" http://java.sun.com/jsf/core" prefix="f" %>
- < %@taglib uri=" http://java.sun.com/jsf/html" prefix="h" %>
- <html>
- <head><title>Thank you</title></head>
- <body>
- <f:view>
- <h3>
- Thank you, <h:outputText value="#{subscriber.name}"/>!
- </h3>
- A confirm mail has been sent to your mail box <h:outputText value="#{subscriber.email}" />.
- </f:view>
- </body>
- </html>
- 編譯工程後,啟動Resin伺服器,就可以直接在浏覽器中輸入 http://localhost:8080/ index.faces,其執行效果如圖7-66和圖7-67所示。
- 圖7-66 圖7-67
- 現在,我們來考慮如何将JSF內建到 Spring架構中。我們注意到,在上面的配置中,Service對象是由JSF定義為全局變量并注入到SubscriberBean中的,我們更希望能從Spring的IoC容器中獲得Service對象,然後将其注入到JSF的SubscriberBean中,這樣使得JSF僅作為Web層與使用者打交道,真正的中間層邏輯和後端持久層的Bean都交給Spring管理,使整個應用程式的層次更加清晰。
- 在Spring中內建JSF非常簡單,其關鍵在于聲明Spring提供的一個Delegating VariableResolver,在faces-config.xml中添加下列内容。
- <faces-config>
- <application>
- <variable-resolver>
- org.springframework.web.jsf.DelegatingVariableResolver
- </variable-resolver>
- </application>
- ...
- </faces-config>
- 我們看看JSF是如何實作依賴注入的。在前面的faces-config.xml中,SubscriberBean需要注入一個Service對象,我們是這麼注入的。
- <managed-property>
- <property-name>service</property-name>
- <value>#{service}</value>
- </managed-property>
- JSF 根據名稱#{service}去查找名稱為service的Managed-Bean,然後将其注入到SubscriberBean中。如果我們聲明了 Spring的DelegatingVariableResolver,則由DelegatingVariableResolver負責查找這個名稱為 service的Bean,DelegatingVariable Resolver就有機會從Spring的IoC容器中獲得名稱為service的Bean,然後交給JSF,JSF再将其注入到 SubscriberBean中,圖7-68很好地說明了DelegatingVariableResolver實作的功能。
- 然後,删除faces-config.xml中定義的Service Bean,因為這個Bean已被放入Spring容器中管理。在Spring的applicationContext.xml中定義這個Service Bean。
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns=" http://www.springframework.org/schema/beans"
- xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
- >
- <bean id="service" class="example.chapter7.ServiceImpl" />
- </beans>
- 最後一步是在web.xml中通過聲明Spring提供的ContextLoaderListener來啟動Spring容器,注意:該Listener應當在其他Listener之前定義,以保證Spring容器首先被啟動。
- <listener>
- <listener-class>
- org.springframework.web.context.ContextLoaderListener
- </listener-class>
- </listener>
- 內建後的整個工程結構如圖7-69所示。
- 無需編譯,重新啟動Resin伺服器後,可以看到和Spring內建的JSF應用的運作效果與前面的JSF應用一緻,不同之處在于Service元件從JSF容器移動到Spring容器内了。
- 需要注意的幾點是, DelegatingVariableResolver将首先試圖查找faces-config.xml中定義的Managed-Bean,找不到才在 Spring的IoC容器中查找。是以,Spring容器中定義的Bean和JSF中定義的Managed-Bean千萬不要有相同的名稱,以免造成沖突。
- 圖7-69
- 另一個值得注意的地方是,作為中間邏輯元件和後端持久化元件的Bean非常适合放在Spring的IoC容器中,以便獲得AOP、聲明式事務等強大的支援。但是,在JSF中作為UI元件Model的 Managed-Bean和UI元件的耦合程度較高,仍适合由JSF管理其生命周期,不推薦放在Spring的IoC容器中。這一點和Struts及 WebWork2的Action不一樣,後兩者的Action要麼是唯一執行個體,要麼是對應每個請求的新執行個體,其生命周期遠沒有JSF的Managed- Bean那麼複雜。
- 有些時候,需要在JSF中手動擷取 Spring容器的Bean,讀者可能已經想到了,由于Spring的IoC容器是綁定在ServletContext上的,是以可以首先通過 FacesContext的getExternalContext()方法獲得ExternalContext執行個體,再通過 ExternalContext的getApplicationMap()方法獲得所有Application級别的Object,再通過查找名稱為 WebApplicationContext.ROOT_ WEB_APPLICATION_CONTEXT_ATTRIBUTE的對象就可以獲得Spring的IoC容器的執行個體,一旦獲得了Spring容器的執行個體,就可以擷取所有的Bean執行個體。
- 事實上,Spring已經提供了一個輔助類FacesContextUtils來擷取ApplicationContext執行個體。
- ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(
- FacesContext.getCurrentInstance());
- 這樣,在JSF的代碼中,任何時候如果需要獲得ApplicationContext的執行個體,就可以按照上述方法調用。