天天看點

Spring內建其他Web架構

Spring內建其他Web架構

  1. 雖然Spring本身也提供了一個功能非常強大的MVC架構,并且和Spring的IoC容器無縫內建,非常便于使用。不過,在實際情況中,我們還不得不考慮衆多采用第三方MVC架構的Web應用程式,例如,采用Strtus、WebWork等。如果要将這些應用程式的邏輯元件和持久化元件納入Spring的IoC容器中進行管理,就必須考慮如何內建這些第三方的MVC架構。Spring強大的內建和擴充能力使得內建第三方MVC架構成為輕而易舉的事情。
  2. 與第三方MVC架構內建時,需要考慮的問題是如何在第三方MVC架構中通路Spring IoC容器管理的Bean,或者說,如何擷取Spring IoC容器的執行個體。我們還要保證在第三方MVC架構開始處理使用者請求之前,Spring的IoC容器必須初始化完畢。
  3. 有兩種方式可以自動加載Spring的IoC 容器。一種是利用第三方架構的擴充點,實作加載Spring的IoC容器,例如,Struts就提供了Plugin擴充。第二種方式是在web.xml中定義Listener或Servlet,讓Web應用程式一啟動就自動加載Spring的IoC容器。這種方式較為通用,對于一些不太常見的第三方MVC 架構,也可以用這種方式來嘗試與Spring內建。
  4. 如果正在使用基于Servlet 2.3或更高規範的Web伺服器,則應當使用Spring提供的ContextLoaderListener來加載IoC容器,因為根據Servlet 2.3規範,所有的Listener都會在Servlet被初始化之前完成初始化。由于我們希望盡可能早地完成Spring IoC容器的初始化,是以,采用ContextLoaderListener加載Spring的IoC容器是最合适的。
  5. 采用ContextLoaderListener加載Spring的IoC容器時,在/WEB-INF/web.xml中定義如下。
  6. <?xml version="1.0" encoding="UTF-8"?>
  7. <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" " http://java.sun.com/dtd/web-app_2_3.dtd">
  8. <web-app>
  9.     ...
  10.     <listener>
  11.         <listener-class>
  12.             org.springframework.web.context.ContextLoaderListener
  13.         </listener-class>
  14.     </listener>
  15.     ...
  16. </web-app>
  17. 預設地,ContextLoaderListener會在/WEB-INF/目錄下查找名為applicationContext. xml檔案,作為Spring的XML配置檔案加載。如果使用其他檔案名,或者有多個XML配置檔案,就需要預先在<context- param>中指定。
  18. <context-param>
  19.     <param-name>contextConfigLocation</param-name>
  20.     <param-value>/WEB-INF/cfg1.xml,/WEB-INF/cfg2.xml</param-value>
  21. </context-param>
  22. 如果使用的Web伺服器還不支援Servlet 2.3規範,則無法使用Listener,也就無法通過ContextLoaderListener來加載Spring的IoC容器。為此,Spring 提供了另一個ContextLoaderServlet,以Servlet的形式來加載Spring的IoC容器。
  23. <servlet>
  24.     <servlet-name>contextLoader</servlet-name>
  25.         <servlet-class>
  26.             org.springframework.web.context.ContextLoaderServlet
  27.         </servlet-class>
  28.         <load-on-startup>0</load-on-startup>
  29. </servlet>
  30. ContextLoaderServlet 查找Spring的XML配置檔案的方式與ContextLoaderListener完全一緻,不過,由于必須首先加載 ContextLoaderServlet,然後加載其他的Servlet,才能保證Spring的IoC容器在其他Servlet處理使用者請求之前初始化完畢。是以,設定<load-on-startup>為0,表示Web伺服器一啟動就加載ContextLoaderServlet,對于其他的Servlet,<load-on-startup>的值要設得大一些,保證ContextLoaderServlet有足夠的時間初始化Spring的IoC容器。
  31. 一旦完成了Spring IoC容器的加載,另一個問題是如何在第三方應用程式中獲得Spring IoC容器的執行個體?
  32. 事實上,不管采用何種方式加載Spring的 IoC容器,Spring最終都會将IoC容器的執行個體綁定到ServletContext上。由于ServletContext在一個Web應用程式中是全局唯一的,是以該Web應用程式中所有的Servlet都可以通路到這個唯一的ServletContext,也就可以獲得Spring IoC容器的執行個體。Spring提供了一個輔助類WebApplicationContextUtils,通過調用 getWebApplicationContext(ServletContext)方法就可以獲得Spring IoC容器的執行個體。對于參數ServletContext,可以在Servlet中随時調用getServletContext()獲得。
  33. 一旦獲得了Spring IoC容器的執行個體,就可以獲得Spring管理的所有的Bean元件。
  34. 下面,我們分别詳細介紹如何在Spring中內建Struts、WebWork2、Tiles和JSF。

    7.6.1  內建Struts

  35. Struts是目前JavaEE Web應用程式中應用最廣泛的MVC開源架構,自從2001年釋出以來,Struts作為JavaEE領域的第一個MVC架構,極大地簡化了基于JSP和 Servlet的Web開發,提供了統一的MVC程式設計模型。雖然從現在看來,Struts的設計模型已不算先進,有許多其他MVC架構擁有更好的設計,但 Struts仍具有龐大的社群支援和最多的開發人員,這些都使得Struts仍是JavaEE領域内Web開發的首選架構。
  36. 不過,Strtus僅僅是一個用于表示層的MVC架構,它并沒有提供一個完整的JavaEE架構的解決方案。如果要将Struts內建到Spring架構中,有兩種方案可供選擇。下面,我們将詳細講解如何将Struts內建到Spring架構中。
  37. 在內建Struts之前,我們假定已經有了一個基于Struts的Web應用程式,這裡我們使用的例子是一個簡單的處理使用者登入的Struts應用,這個Struts應用在浏覽器中的效果如圖7-54所示。
  38. 圖7-54
  39. 在Eclipse中,我們建立了這個名為Struts的工程,其結構如圖7-55所示。
  40. 圖7-55
  41. 在Struts 應用中,每個使用者請求通過一個Action類來處理,這和Spring的Controller類似,但是Struts的Action是一個類而非接口,是以,所有的Action子類都隻能從Action派生。由于Struts是一個Web層架構,需要考慮如何獲得業務邏輯接口。一個較好的設計是首先設計一個BaseAction,其中定義了獲得業務邏輯接口的方法,其他所有的Action從BaseAction派生即可非常友善地調用業務邏輯接口。
  42. public class BaseAction extends Action {
  43.     private static final UserService userService = new UserServiceImpl();
  44.     public UserService getUserService() {
  45.         return userService;
  46.     }
  47. }
  48. 在BaseAction 中,我們以靜态變量持有業務邏輯接口UserService的引用,這是一個不太優雅的設計。如果使用Spring的IoC容器來配置和管理這些邏輯元件,則可以完全實作一個優雅的多層應用程式,Struts隻處理Web表示層,業務邏輯層和資料持久層都交由Spring管理,便于維護和擴充。
  49. 在Spring中有兩種方式來內建 Struts,關于Struts的更多詳細用法的讨論已經超出了本書的範圍。在本節中,假定讀者對Struts已經有了一定的基礎。下面我們分别介紹 Spring內建Struts的兩種方案,兩種方案都需要Spring提供的一個名為ContextLoader Plugin的Struts插件來啟動IoC容器。由于是Web應用程式,ContextLoaderPlugin啟動的是 WebApplicationContext的執行個體。
  50. 第一種方案是通過Spring提供的一個Struts插件來初始化IoC容器,然後從Spring提供的ActionSupport派生所有的 Action,以便能通過getWebApplicationContext()獲得ApplicationContext,一旦獲得了 ApplicationContext引用,就可以獲得Spring的IoC容器中所有的Bean。我們建立一個Struts_Spring1工程,首先複制Struts工程的所有檔案,然後在Struts配置檔案的最後添加Spring的插件聲明。
  51. <struts-config>
  52.     ...
  53.     <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn" />
  54. </struts-config>
  55. 然後修改BaseAction,将其超類從Struts的Action改為Spring的ActionSupport。
  56. public class BaseAction extends ActionSupport {
  57.     public UserService getUserService() {
  58.         return (UserService) getWebApplicationContext()
  59.                .getBean("userService");
  60.     }
  61. }
  62. 現在,BaseAction就可以随時通過Spring的ApplicationContext獲得邏輯元件UserService的引用,這樣所有的Action子類都可以直接通過getUserService()獲得UserService元件。
  63. 最後一步是編寫Spring的XML配置檔案,預設的檔案名是<servlet-name> -servlet.xml,由于我們在web.xml中配置Struts的ActionServlet名稱為action,是以,Spring的配置檔案為action-servlet.xml,放到web/WEB-INF/目錄下,定義所有的Bean元件,但不包括Struts的Action執行個體。
  64. <?xml version="1.0" encoding="UTF-8"?>
  65. <beans xmlns=" http://www.springframework.org/schema/beans"
  66.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  67.        xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  68.     <bean id="userService" class="example.chapter7.UserServiceImpl" />
  69. </beans>
  70. 如果Spring的XML配置檔案沒有使用預設的檔案名,就必須在Struts配置檔案中聲明Plugin時指定檔案位置。
  71. <struts-config>
  72.     ...
  73.     <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
  74.         <set-property property="contextConfigLocation"
  75.             value="/WEB-INF/my-spring-config.xml"/>
  76.     </plug-in>
  77. </struts-config>
  78. 整個Struts_Spring1工程的結構如圖7-56所示。
  79. 圖7-56
  80. 編譯工程,然後啟動Resin伺服器,可以看到效果和原始的Struts工程一樣,但是,業務邏輯層元件和持久層元件(在本例中,為了簡化問題,沒有設計持久層元件)已被納入Spring的IoC容器之中,進而清晰地将表示層和業務層劃分開來。
  81. 使用這種方式內建Spring和Struts 時,如果合理地抽象出一個類似BaseAction的類作為所有Action的超類,在內建到Spring時,隻需将BaseAction的超類從 Struts的Action類改為Spring提供的ActionSupport,然後在Struts中聲明Spring的Plugin,就可以在不修改任何Action的情況下實作和Spring的內建。
  82. 第二種內建Struts的方案是将 Struts的所有Action都作為Spring IoC容器中的Bean來管理,就像Spring的Controller一樣。然後通過Spring提供的 DelegatingRequestProcessor來替代Struts的預設派發器,以便讓Spring能截獲派發給Action的請求。在這種方式下,業務邏輯元件通過依賴注入的方式在Spring的IoC容器中就配置完畢了。
  83. 我們仍建立一個Struts_Spring2的工程,将Struts工程的所有檔案複制過來,其目錄結構和Struts_Spring1類似,如圖7-57所示。
  84. 圖7-57
  85. 為了能将業務元件UserService注入到每個Action類中,抽象出一個BaseAction是非常重要的,這樣可以使代碼的修改被限制在BaseAction中,而不會涉及每一個Action類。修改BaseAction如下。
  86. public class BaseAction extends Action {
  87.     private UserService userService;
  88.     public void setUserService(UserService userService) {
  89.         this.userService = userService;
  90.     }
  91.     public UserService getUserService() {
  92.         return userService;
  93.     }
  94. }
  95. 然後修改Struts的配置檔案,添加Spring提供的DelegatingRequestProcessor和Plugin的聲明。
  96. <struts-config>
  97.     ...
  98.     <controller processorClass="org.springframework.web.struts.Delegating RequestProcessor"/>
  99.     ...
  100.     <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn" />
  101. </struts-config>
  102. Spring 提供的Struts Plugin仍負責啟動Spring容器,而DelegatingRequestProcessor可以讓Spring接管請求,進而将請求派發到IoC 容器管理的Action執行個體。為此,在Spring的XML配置檔案中,除了定義業務邏輯元件和其他元件外,還必須聲明每一個Action類,其name 屬性和Struts配置檔案中的path要完全一緻。由于我們在Struts配置檔案中定義了兩個Action:
  103. <action-mappings>
  104.     <action path="/login" type="example.chapter7.struts.LoginAction" ... />
  105.     <action path="/logout" type="example.chapter7.struts.LogoutAction" ... />
  106. </action-mappings>
  107. 是以,在Spring的配置檔案中,除UserService元件外,還必須定義兩個Action,并且要和Struts配置檔案中的Action一一對應。
  108. <?xml version="1.0" encoding="UTF-8"?>
  109. <beans xmlns=" http://www.springframework.org/schema/beans"
  110.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  111.        xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  112.     <bean id="userService" class="example.chapter7.UserServiceImpl" />
  113.     <bean name="/login" class="example.chapter7.struts.LoginAction">
  114.         <property name="userService" ref="userService" />
  115.     </bean>
  116.     <bean name="/logout" class="example.chapter7.struts.LogoutAction">
  117.         <property name="userService" ref="userService" />
  118.     </bean>
  119. </beans>
  120. 實際上,如果使用這種方案實作內建,Struts配置檔案中Action的type屬性将被忽略,因為Spring會根據path屬性查找對應name的Action Bean。但是,仍然建議将type屬性辨別出來,便于檢視URL和Action的關聯。
  121. 使用這種方案時,由于Action被作為Bean納入Spring的IoC容器管理,是以,可以獲得完全的依賴注入能力。不過,最大的不便是所有的Action必須同時在Struts和Spring的配置檔案中各配置一次,這增加了配置檔案的維護成本。
  122. 在內建Struts時,選擇何種方案需要根據實際情況決定。例如,由于RequestProcessor是Struts的一個擴充點,如果現有的Web應用程式已經擴充了 RequestProcessor,采用第一種方式就比較合适。不過,無論采用哪種方案,基于Struts的整體設計至關重要,如果沒有統一的業務邏輯接口,而是将業務邏輯散落在各個Action中,則對代碼的修改将涉及所有的Action類。

    7.6.2  內建WebWork2

  123. WebWork是一個非常簡潔和優雅的Web 架構,WebWork的架構設計非常容易了解,它建構在一個指令模式的XWork架構之上,支援多種視圖技術,并且WebWork也有一個豐富的标簽庫,能非常容易地實作校驗。WebWork最大的特色就是使用IoC容器來管理所有的Action,并且擁有攔截器等一系列類似AOP的概念,是以, WebWork的整體設計比Struts更優秀,也更容易擴充。
  124. WebWork目前有兩個主要版本: WebWork 1.x和WebWork 2.x,兩個版本的差異較大,本節介紹的均以最新的WebWork 2.2為例。可以從WebWork的官方站點 http://www. opensymphony.com/webwork/下載下傳最新的2.2.4版。
  125. 在WebWork 2.2版本之前,WebWork有一個自己的IoC容器,盡管仍可以和Spring內建,但是不那麼友善。從WebWork 2.2開始,WebWork的設計者就推薦使用Spring的IoC容器來管理WebWork2的Action,這對于使用Spring架構的開發者來說無疑是一個好消息,因為這使得兩者的內建更加容易,我們可以像對待Spring的Bean一樣,在Spring的IoC容器中直接配置WebWork2的 Action。
  126. WebWork2的開發團隊已經提供了一個 Spring和WebWork2的內建方案,正因為如此,在Spring架構中并沒有WebWork2相關的支援包。如果讀者在內建WebWork2時遇到問題,請首先參考WebWork官方網站( http://www.opensymphony.com/webwork/)的相關文檔。
  127. 如果讀者正在考慮使用WebWork2作為 MVC架構,則理所當然應當內建到Spring架構中。在下面的例子中,我們以最新的WebWork 2.2.4為例,首先建立一個基于WebWork2的Web應用程式,命名為WebWork2,其Eclipse工程結構如圖7-58所示。
  128. 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應用程式的可測試性。
  129. 圖7-58
  130. 我們假定讀者對WebWork2已經有了一定的了解。在這個Web應用程式中,我們定義了兩個Action,ListBooksAction用于列出所有的書籍,BookDetailAction用于檢視書籍的詳細資訊,為此,我們還定義了一個BookService接口,并在所有Action的超類 BaseAction中定義了擷取BookService接口的方法getBookService()。
  131. public abstract class BaseAction implements Action {
  132.     private static BookService bookService = new BookServiceImpl();
  133.     public BookService getBookService() {
  134.         return bookService;
  135.     }
  136. }
  137. 然後,在xwork.xml中配置好兩個Action,并放到src目錄下,這樣,編譯後該檔案就位于/WEB-INF/classes目錄下。
  138. <?xml version="1.0" encoding="UTF-8"?>
  139. <!DOCTYPE xwork
  140.           PUBLIC
  141.           "-//OpenSymphony Group//XWork 1.0//EN"
  142.           " http://www.opensymphony.com/xwork/xwork-1.0.dtd">
  143. <xwork>
  144.     <include file="webwork-default.xml"/>
  145.     <package name="default" extends="webwork-default">
  146.         <action name="listBooks" class="example.chapter7.webwork2.ListBooksAction">
  147.             <result name="success" type="dispatcher">/listBooks.jsp</result>
  148.         </action>
  149.         <action name="bookDetail" class="example.chapter7.webwork2. BookDetailAction">
  150.             <result name="success" type="dispatcher">/bookDetail.jsp</result>
  151.             <result name="error" type="dispatcher">/notFound.jsp</result>
  152.         </action>
  153.     </package>
  154. </xwork>
  155. 在web.xml中,我們聲明WebWork2的ServletDispatcher,并映射所有以.action結尾的URL。
  156. <?xml version="1.0" encoding="UTF-8"?>
  157. <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" " http://java.sun.com/dtd/web-app_2_3.dtd">
  158. <web-app>
  159.     <servlet>
  160.         <servlet-name>dispatcher</servlet-name>
  161.         <servlet-class>
  162.             com.opensymphony.webwork.dispatcher.ServletDispatcher
  163.         </servlet-class>
  164.         <load-on-startup>0</load-on-startup>
  165.     </servlet>
  166.     <servlet-mapping>
  167.         <servlet-name>dispatcher</servlet-name>
  168.         <url-pattern>*.action</url-pattern>
  169.     </servlet-mapping>
  170.     <taglib>
  171.         <taglib-uri>webwork</taglib-uri>
  172.         <taglib-location>/WEB-INF/lib/webwork-2.2.4.jar</taglib-location>
  173.     </taglib>
  174. </web-app>
  175. 現在,作為一個獨立的WebWork2的應用程式,我們已經可以在浏覽器中看到實際效果了。啟動Resin伺服器,然後輸入位址“http://localhost:8080/listBooks.action”,如圖7-59所示。
  176. 我們還沒有将Spring內建進來,并且和7.6.1節的Struts應用程式類似,擷取BookService接口設計得不那麼優雅。下一步,我們将WebWork2與Spring內建起來,讓Spring來管理所有的Action和其他邏輯元件。
  177. 圖7-59
  178. 我們将WebWork2工程複制一份,命名為WebWork2_Spring,然後開始一步一步地将其內建到Spring架構中。
  179. 在WebWork2中內建Spring是一件非常容易的事情。在WebWork2中,所有的Action也是由IoC容器管理的、預設地,WebWork2自身有一個IoC容器,負責管理所有的 Action,但是,正如前面提到的,從WebWork 2.2開始,WebWork2設計者推薦使用Spring的IoC容器來管理Action。隻要對配置檔案稍做修改,就可以讓WebWork2直接使用 Spring的IoC容器。
  180. 第一步是編寫一個webwork.properties的配置檔案,放在src目錄下,指定WebWork2使用的IoC容器名稱。
  181. webwork.objectFactory = spring
  182. 該配置檔案非常簡單,隻需要以上一行内容即可。更多的配置選項可以參考WebWork2的文檔。編譯後,該檔案就會被放到/WEB-INF/classes/目錄下。
  183. 現在,隻要Spring的IoC容器啟動了,WebWork2就會自動感覺并使用Spring的IoC容器。要啟動Spring的IoC容器,使用 ContextLoaderListener最合适不過,在web.xml中添加Spring的ContextLoaderListener聲明。
  184. <listener>
  185.     <listener-class>
  186.         org.springframework.web.context.ContextLoaderListener
  187.     </listener-class>
  188. </listener>
  189. 下一步是在Spring預設的XML配置檔案中定義所有的Bean,包括WebWork2的Action。由于使用 ContextLoaderListener來啟動Spring的IoC容器,是以,預設的XML配置檔案名稱為 applicationContext.xml,内容如下。
  190. <?xml version="1.0" encoding="UTF-8"?>
  191. <beans xmlns=" http://www.springframework.org/schema/beans"
  192.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  193.        xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  194. >
  195.     <bean id="bookService" class="example.chapter7.BookServiceImpl" />
  196.     <bean id="listBooksAction" class="example.chapter7.webwork2. ListBooks Action" scope="prototype">
  197.         <property name="bookService" ref="bookService" />
  198.     </bean>
  199.     <bean id="bookDetailAction" class="example.chapter7.webwork2. BookDetail Action" scope="prototype">
  200.         <property name="bookService" ref="bookService" />
  201.     </bean>
  202. </beans>
  203. 要特别注意的是,WebWork2使用的Action和Struts不同,對于每個使用者請求,WebWork2都會建立一個新的Action執行個體來處理使用者請求,是以,必須将Action的scope定義為prototype,使得每次WebWork2向Spring IoC容器請求一個Action時,Spring都能傳回一個新的執行個體。采用Bean模闆也不失為一個好辦法,具體配置請參考第3章3.9.2“使用模闆裝配”一節。
  204. 最後一步是修改xwork.xml,将每個Action的class屬性從類名改為Spring中對應的id,這樣,WebWork2就會根據此id向Spring的IoC容器請求一個新的Action執行個體。
  205. <?xml version="1.0" encoding="UTF-8"?>
  206. <!DOCTYPE xwork
  207.           PUBLIC
  208.           "-//OpenSymphony Group//XWork 1.0//EN"
  209.           " http://www.opensymphony.com/xwork/xwork-1.0.dtd">
  210. <xwork>
  211.     <include file="webwork-default.xml"/>
  212.     <package name="default" extends="webwork-default">
  213.         <action name="listBooks" class="listBooksAction">
  214.             <result name="success" type="dispatcher">/listBooks.jsp</result>
  215.         </action>
  216.         <action name="bookDetail" class="bookDetailAction">
  217.             <result name="success" type="dispatcher">/bookDetail.jsp</result>
  218.             <result name="error" type="dispatcher">/notFound.jsp</result>
  219.         </action>
  220.     </package>
  221. </xwork>
  222. 完成了以上步驟後,将spring.jar放入/WEB-INF/lib目錄下,然後啟動Resion伺服器,就可以看到如下輸出。
  223. [2006/12/06 16:41:43.138] Initializing WebWork-Spring integration...
  224. [2006/12/06 16:41:43.154] Setting autowire strategy to name
  225. [2006/12/06 16:41:43.154] ... initialized WebWork-Spring integration successfully
  226. 打開浏覽器測試,其結果與前面WebWork2工程的結果完全相同,不過,所有的Action和邏輯元件都由Spring的IoC來管理,這使得應用程式的層次更加清晰。WebWork2_Spring工程的整個結構如圖7-60所示。
  227. 圖7-60
  228. 預設情況下,在使用Spring的IoC容器時,WebWork2使用byName的自動裝配功能來裝配WebWork2的Action。可以根據需要将其修改為byType。如果Action較多,更好的配置方法是首先定義一個模闆Bean,作為所有Action Bean的模闆。
  229. <bean id="webworkActionTemplate" abstract="true" scope="prototype">
  230.     <property name="service1" ref="service1" />
  231.     <property name="service2" ref="service2" />
  232. </bean>
  233. 其餘的Action Bean通過繼承此模闆Bean就可以安全地獲得scope設定和其他依賴注入的屬性。

    7.6.3  內建Tiles

  234. 前面我們講到了JavaEE Web元件處理請求的3種方式:轉發(Forward)、包含(Include)和錯誤(Error),Tiles架構将Include方式發揮到了極緻。Tiles并不是一個MVC架構,從本質上說,Tiles是一個模闆架構,它将頁面分成幾個小的部分,然後動态地将它們組合在一起,進而允許更靈活地建立可以重用的頁面元件。
  235. 雖然Tiles是Struts架構的一部分,但是Tiles從一開始就被設計為能夠在Struts外獨立适用。事實上,Tiles可以用于任何Web架構,當然也包括Spring的MVC架構。
  236. Spring已經對Tiles做了非常好的內建,在Spring架構中使用Tiles更加友善。下面的例子我們建立了一個Spring_Tiles工程,結構如圖7-61所示。
  237. 圖7-61
  238. 将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。
  239. <?xml version="1.0" encoding="UTF-8"?>
  240. <!DOCTYPE web-app PUBLIC
  241.     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  242.     " http://java.sun.com/dtd/web-app_2_3.dtd">
  243. <web-app>
  244.     <servlet>
  245.         <servlet-name>dispatcher</servlet-name>
  246.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</ servlet-class>
  247.         <load-on-startup>0</load-on-startup>
  248.     </servlet>
  249.     <servlet-mapping>
  250.         <servlet-name>dispatcher</servlet-name>
  251.         <url-pattern>*.do</url-pattern>
  252.     </servlet-mapping>
  253.     <taglib>
  254.         <taglib-uri>http://struts.apache.org/tags-tiles</taglib-uri>
  255.         <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
  256.     </taglib>
  257. </web-app>
  258. 我們先來看看如何在Tiles中組合出一個頁面。在這個Web應用程式中,我們一共設計了兩個頁面,一個是登入頁面,一個是歡迎頁面,每個頁面都被分為頁眉(header)、主體(body)和頁腳(footer)三部分。為了能在Tiles中組合出一個完整的頁面,我們分别編寫header.jsp作為頁眉,footer.jsp作為頁腳,在登入頁中,主體部分是login.jsp,而在歡迎頁中,主體是welcome.jsp,如圖7-62所示。
  259. 通過将頁面拆為幾個部分,我們就可以複用頁面的公共部分(如header.jsp和footer.jsp),在Tiles中,每個可重用的部分被稱為一個可視化元件,通常是一個JSP檔案,然後用一個模闆将幾個元件組合到一起,就構成了一個完整的頁面。例如,template.jsp将頁眉、主體和頁腳3個元件組合在一起,作為一個完整的頁面展示給使用者。
  260. <%@ page contentType="text/html; charset=utf-8" %>
  261. <%@ taglib prefix="tiles" uri=" http://struts.apache.org/tags-tiles" %>
  262. <html>
  263. <head>
  264. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  265. <title><tiles:getAsString name="title" /></title>
  266. </head>
  267. <body>
  268. <table width="100%" cellspacing="0" cellpadding="0">
  269.     <tr><td><tiles:insert name="header" /></td></tr>
  270.     <tr><td><tiles:insert name="body" /></td></tr>
  271.     <tr><td><tiles:insert name="footer" /></td></tr>
  272. </table>
  273. </body>
  274. </html>
  275. 組合tile非常直覺,用<tiles:insert>标簽就可以嵌入一個tile,用<tiles:getAsString>可以将一個屬性寫到頁面中。在template.jsp模闆裡,一共插入了3個元件和1個屬性值作為标題。
  276. 在template.jsp 中,我們隻定義了每個元件的名稱,并沒有指定具體的jsp檔案。在Tiles中,每個頁面的布局都被定義在XML配置檔案中,我們将Tiles的配置檔案命名為tiles-defs.xml,并放到/web/WEB-INF/目錄下,在這個配置檔案中,我們定義了login和welcome兩個完整的頁面。
  277. <?xml version="1.0" encoding="UTF-8"?>
  278. <!DOCTYPE tiles-definitions PUBLIC
  279.        "-//Apache Software Foundation//DTD Tiles Configuration 1.3//EN"
  280.        " http://struts.apache.org/dtds/tiles-config_1_3.dtd">
  281. <tiles-definitions>
  282.     <definition name="default" path="/template.jsp">
  283.         <put name="title"  value="Default Title" />
  284.         <put name="header" value="/header.jsp" />
  285.         <put name="footer" value="/footer.jsp" />
  286.     </definition>
  287.     <definition name="login" extends="default" >
  288.         <put name="title" value="Please login" />
  289.         <put name="body"  value="/login.jsp" />
  290.     </definition>
  291.     <definition name="welcome" extends="default">
  292.         <put name="title" value="Welcome!" />
  293.         <put name="body"  value="/welcome.jsp" />
  294.     </definition>
  295. </tiles-definitions>
  296. 每個 <definition>定義一個完整的頁面,在Tiles中,頁面是可以繼承的,例如,上述default頁面定義的header和 footer分别為header.jsp和footer.jsp,并設定頁面的title屬性為“Default Title”,login頁面就可以從default繼承,并定義body為login.jsp,然後覆寫了title屬性。通過繼承,就使得頁面定義更加簡潔。
  297. 最後一步是在Spring的XML配置檔案中配置Tiles架構,并選擇一個合适的ViewResolver,讓它能解析Tiles視圖。
  298. <bean id="tilesConfig" class="org.springframework.web.servlet.view. tiles.TilesConfigurer">
  299.     <property name="definitions">
  300.         <list>
  301.             <value>/WEB-INF/tiles-defs.xml</value>
  302.         </list>
  303.     </property>
  304. </bean>
  305. <bean id="viewResolver" class="org.springframework.web.servlet.view. InternalResourceViewResolver">
  306.     <property name="viewClass" value="org.springframework.web.servlet.view. tiles.TilesView" />
  307. </bean>
  308. TilesConfigurer隻需要指定Tiles配置檔案就可以自動地配置好Tiles,為了讓視圖解析器能識别TilesView,使用InternalResourceViewResolver并将TilesView綁定即可。
  309. LoginController和 WelcomeController的編寫和前面的完全相同,在LoginController中,傳回的視圖名稱是“login”,而 WelcomeController傳回的視圖名稱是“welcome”,細心的讀者可能已經發現了,視圖解析器的任務就是在Tiles配置檔案中查找對應名稱的頁面,然後根據頁面的定義,由TilesView将整個頁面渲染出來。兩個頁面的效果如圖7-63和圖7-64所示。
  310. 圖7-63                                     圖7-64

    7.6.4  內建JSF

  311. JSF是JavaServer Faces的縮寫,JSF是JavaEE中建構Web應用程式的又一個全新的MVC架構。與其他常見的MVC架構相比,JSF提供了一種以元件為中心的方式來開發Web應用程式。JSF的目标是提供一種類似ASP.Net的可視化Web元件,開發人員隻需要将已有的JSF元件拖放到頁面上,就可以迅速建立一個Web應用程式,而不必從頭開始編寫UI界面。是以,JSF的易用性很大程度上取決于一個簡單、高效的可視化開發環境。
  312. 和傳統的MVC架構(如Struts)相比, JSF更像是一個Web版本的Swing應用程式。傳統的MVC架構的View是以頁面為中心的,而JSF的View則是一系列可複用的UI元件。是以, JSF應用程式的生命周期更為複雜。一般來說,JSF處理使用者請求的流程如圖7-65所示。
  313. 除了恢複視圖和渲染響應是必須的之外,其他步驟都可以視情況跳過。例如,在驗證失敗後,就直接跳到渲染響應,将錯誤資訊展示給使用者。
  314. JSF的真正威力在于它的UI界面元件。與 ASP.NET類似,JSF的UI元件使開發人員能夠使用已建立好的UI元件(如JSF内置的UI元件)來建立Web頁面,而非完全從頭建立,進而提供了前所未有的開發效率。JSF的UI元件可以是簡單到隻是顯示文本的outputLabel,或者複雜到可以表示來自資料庫表的表格。
  315. 此外,JSF也使用類似Spring IoC容器的方式來管理Model,即與UI元件綁定的Bean,在JSF中稱為Managed-Bean,并且也支援依賴注入。Managed- Bean的生命周期可以是request、session和application,分别對應一次請求、一次會話和整個Web應用程式生存期。
  316. 在開發JSF應用之前,我們需要首先準備開發環境。由于JSF也是一個規範,不同的廠商都可以有自己的實作。這裡我們采用的是JSF 1.1規範(JSR 127),因為JSF 1.1僅需要Servlet 2.3規範的支援,在大多數Web伺服器上都能正常運作,而最新的JSF 1.2則要求Servlet 2.5規範,目前除了少數伺服器外,大多數伺服器還無法支援。
  317. 我們還需要一個JSF 1.1的實作。SUN給出了一個JSF 1.1的參考實作,可以用于開發,以保證我們的JSF應用程式将來可以移植到其他的JSF實作。Apache也提供了一個MyFaces的JSF實作,讀者可以參考Apache的官方站點擷取詳細資訊,本書不對此做更多讨論。
  318. 下面的例子改自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所示。
  319. 圖7-66
  320. 編譯工程隻需要引用jsf-api.jar。幾個主要的接口和類如下。
  321. Service接口是唯一的業務邏輯接口,它僅定義了一個subscribe方法。
  322. public interface Service {
  323.     void subscribe(SubscriberBean subscriber);
  324. }
  325. 其實作類ServiceImpl僅僅簡單地列印出使用者的訂閱資訊。
  326. public class ServiceImpl implements Service {
  327.     public void subscribe(SubscriberBean subscriber) {
  328.         System.out.println("User /"" + subscriber.getName()
  329.                 + "/" with email /"" + subscriber.getEmail()
  330.                 + "/" subscribed successfully with preferred language "
  331.                 + subscriber.getLanguage());
  332.     }
  333. }
  334. SubscriberBean 是一個與前端UI綁定的Session範圍的Managed-Bean,其作用範圍是Session,在SubscriberBean中還定義了 submit()方法來處理JSF的Action,是以,在SubscriberBean中必須要注入一個Service對象,才能完成實際業務方法的調用。
  335. public class SubscriberBean {
  336.     private String name;
  337.     private String email;
  338.     private String language;
  339.     private Service service = new ServiceImpl();
  340.     public void setService(Service service) {
  341.         this.service = service;
  342.     }
  343.     public String getName() { return name; }
  344.     public void setName(String name) { this.name = name; }
  345.     public String getEmail() { return email; }
  346.     public void setEmail(String email) { this.email = email; }
  347.     public String getLanguage() { return language; }
  348.     public void setLanguage(String language) { this.language = language; }
  349.     public String submit() {
  350.         // 處理訂閱請求:
  351.         service.subscribe(this);
  352.         return "success";
  353.     }
  354. }
  355. EmailValidator是一個自定義的JSF驗證器,讀者可以參考JSF相關文檔獲得有關驗證器的使用方法,這裡僅給出實作。
  356. public class EmailValidator implements Validator {
  357.     public void validate(FacesContext context, UIComponent component, Object value)
  358.             throws ValidatorException {
  359.         String email = ((String)value).trim();
  360.         if(!email.matches("[a-zA-Z0-9][//w//.//-]*@[a-zA-Z0-9][//w//.//-] *//.[a-zA-Z][a-zA-Z//.]*")) {
  361.             throw new ValidatorException(
  362.                     new FacesMessage("Invalid email address"));
  363.         }
  364.     }
  365. }
  366. 下面我們需要配置JSF,首先,在web.xml中聲明JSF相關Listener和FacesServlet。
  367. <?xml version="1.0" encoding="UTF-8"?>
  368. <!DOCTYPE web-app PUBLIC
  369.   "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  370.   " http://java.sun.com/dtd/web-app_2_3.dtd">
  371. <web-app>
  372.     <context-param>
  373.         <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
  374.         <param-value>client</param-value>
  375.     </context-param>
  376.     <context-param>
  377.         <param-name>com.sun.faces.validateXml</param-name>
  378.         <param-value>true</param-value>
  379.     </context-param>
  380.     <listener>
  381.         <listener-class>
  382.             com.sun.faces.config.ConfigureListener
  383.         </listener-class>
  384.     </listener>
  385.     <!-- Faces Servlet -->
  386.     <servlet>
  387.         <servlet-name>Faces Servlet</servlet-name>
  388.         <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  389.         <load-on-startup>0</load-on-startup>
  390.     </servlet>
  391.     <servlet-mapping>
  392.         <servlet-name>Faces Servlet</servlet-name>
  393.         <url-pattern>*.faces</url-pattern>
  394.     </servlet-mapping>
  395. </web-app>
  396. FacesServlet 是整個JSF應用程式的前端入口,負責接收所有的使用者請求。然後,需要編寫一個預設的faces-config.xml配置檔案,告訴JSF所有的配置資訊,包括自定義的驗證器、導航規則、所有的Managed-Bean和這些Bean之間的依賴關系。将faces-config.xml放到/WEB- INF/目錄下。
  397. <?xml version="1.0" encoding="UTF-8"?>
  398. <!DOCTYPE faces-config PUBLIC
  399.   "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  400.   " http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
  401. <faces-config>
  402.     <!-- 聲明自定義的Validator -->
  403.     <validator>
  404.         <validator-id>emailValidator</validator-id>
  405.         <validator-class>example.chapter7.EmailValidator</validator-class>
  406.     </validator>
  407.     <!-- 聲明所有的Managed Bean -->
  408.     <managed-bean>
  409.         <managed-bean-name>service</managed-bean-name>
  410.         <managed-bean-class>example.chapter7.ServiceImpl</managed-bean-class>
  411.         <managed-bean-scope>application</managed-bean-scope>
  412.     </managed-bean>
  413.     <managed-bean>
  414.         <managed-bean-name>subscriber</managed-bean-name>
  415.         <managed-bean-class>example.chapter7.SubscriberBean</managed-bean-class>
  416.         <managed-bean-scope>session</managed-bean-scope>
  417.         <managed-property>
  418.             <property-name>service</property-name>
  419.             <value>#{service}</value>
  420.         </managed-property>
  421.     </managed-bean>
  422.     <!-- 定義導航規則 -->
  423.     <navigation-rule>
  424.         <from-view-id>/index.jsp</from-view-id>
  425.         <navigation-case>
  426.             <from-outcome>success</from-outcome>
  427.             <to-view-id>/thanks.jsp</to-view-id>
  428.         </navigation-case>
  429.     </navigation-rule>
  430. </faces-config>
  431. 下一步是編寫兩個JSP頁面,使用JSF标準的标簽庫,讀者可以發現JSF使用的表達式和Velocity非常類似,不過将“$”改為了“#”。index.jsp負責接受使用者輸入并驗證表單。
  432. < %@page contentType="text/html;charset=UTF-8" %>
  433. < %@taglib uri=" http://java.sun.com/jsf/core" prefix="f" %>
  434. < %@taglib uri=" http://java.sun.com/jsf/html" prefix="h" %>
  435. <html>
  436. <head>
  437. <title>Subscribe Form</title>
  438. </head>
  439. <body>
  440.     <f:view>
  441.         <h:form>
  442.             <h4>Subscribe</h4>
  443.             Your name:
  444.             <h:inputText id="id_name" value="#{subscriber.name}" required="true">
  445.                 <f:validateLength minimum="3" maximum="20" />
  446.             </h:inputText>
  447.             <h:message for="id_name" />
  448.             <br/>
  449.             Your email:
  450.             <h:inputText id="id_email" value="#{subscriber.email}" required="true">
  451.                 <f:validator validatorId="emailValidator" />
  452.             </h:inputText>
  453.             <h:message for="id_email" />
  454.             <br/>
  455.             Preferred Language:
  456.             <h:selectOneMenu value="#{subscriber.language}" required="true">
  457.                 <f:selectItem itemLabel="English" itemValue="English" />
  458.                 <f:selectItem itemLabel="Chinese" itemValue="Chinese" />
  459.             </h:selectOneMenu>
  460.             <br/>
  461.             <h:commandButton type="submit" value="Submit" action="#{subscriber. submit}" />
  462.         </h:form>
  463.     </f:view>
  464. </body>
  465. </html>
  466. thanks.jsp用于提示使用者訂閱成功。
  467. < %@page contentType="text/html;charset=UTF-8" %>
  468. < %@taglib uri=" http://java.sun.com/jsf/core" prefix="f" %>
  469. < %@taglib uri=" http://java.sun.com/jsf/html" prefix="h" %>
  470. <html>
  471. <head><title>Thank you</title></head>
  472. <body>
  473.     <f:view>
  474.         <h3>
  475.             Thank you, <h:outputText value="#{subscriber.name}"/>!
  476.         </h3>
  477.         A confirm mail has been sent to your mail box <h:outputText value="#{subscriber.email}" />.
  478.     </f:view>
  479. </body>
  480. </html>
  481. 編譯工程後,啟動Resin伺服器,就可以直接在浏覽器中輸入 http://localhost:8080/ index.faces,其執行效果如圖7-66和圖7-67所示。
  482. 圖7-66                                     圖7-67
  483. 現在,我們來考慮如何将JSF內建到 Spring架構中。我們注意到,在上面的配置中,Service對象是由JSF定義為全局變量并注入到SubscriberBean中的,我們更希望能從Spring的IoC容器中獲得Service對象,然後将其注入到JSF的SubscriberBean中,這樣使得JSF僅作為Web層與使用者打交道,真正的中間層邏輯和後端持久層的Bean都交給Spring管理,使整個應用程式的層次更加清晰。
  484. 在Spring中內建JSF非常簡單,其關鍵在于聲明Spring提供的一個Delegating VariableResolver,在faces-config.xml中添加下列内容。
  485. <faces-config>
  486.     <application>
  487.         <variable-resolver>
  488.             org.springframework.web.jsf.DelegatingVariableResolver
  489.         </variable-resolver>
  490.     </application>
  491.     ...
  492. </faces-config>
  493. 我們看看JSF是如何實作依賴注入的。在前面的faces-config.xml中,SubscriberBean需要注入一個Service對象,我們是這麼注入的。
  494. <managed-property>
  495.     <property-name>service</property-name>
  496.     <value>#{service}</value>
  497. </managed-property>
  498. JSF 根據名稱#{service}去查找名稱為service的Managed-Bean,然後将其注入到SubscriberBean中。如果我們聲明了 Spring的DelegatingVariableResolver,則由DelegatingVariableResolver負責查找這個名稱為 service的Bean,DelegatingVariable Resolver就有機會從Spring的IoC容器中獲得名稱為service的Bean,然後交給JSF,JSF再将其注入到 SubscriberBean中,圖7-68很好地說明了DelegatingVariableResolver實作的功能。
  499. 然後,删除faces-config.xml中定義的Service Bean,因為這個Bean已被放入Spring容器中管理。在Spring的applicationContext.xml中定義這個Service Bean。
  500. <?xml version="1.0" encoding="UTF-8"?>
  501. <beans xmlns=" http://www.springframework.org/schema/beans"
  502.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  503.        xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
  504. >
  505.     <bean id="service" class="example.chapter7.ServiceImpl" />
  506. </beans>
  507. 最後一步是在web.xml中通過聲明Spring提供的ContextLoaderListener來啟動Spring容器,注意:該Listener應當在其他Listener之前定義,以保證Spring容器首先被啟動。
  508. <listener>
  509.     <listener-class>
  510.         org.springframework.web.context.ContextLoaderListener
  511.     </listener-class>
  512. </listener>
  513. 內建後的整個工程結構如圖7-69所示。
  514. 無需編譯,重新啟動Resin伺服器後,可以看到和Spring內建的JSF應用的運作效果與前面的JSF應用一緻,不同之處在于Service元件從JSF容器移動到Spring容器内了。
  515. 需要注意的幾點是, DelegatingVariableResolver将首先試圖查找faces-config.xml中定義的Managed-Bean,找不到才在 Spring的IoC容器中查找。是以,Spring容器中定義的Bean和JSF中定義的Managed-Bean千萬不要有相同的名稱,以免造成沖突。
  516. 圖7-69
  517. 另一個值得注意的地方是,作為中間邏輯元件和後端持久化元件的Bean非常适合放在Spring的IoC容器中,以便獲得AOP、聲明式事務等強大的支援。但是,在JSF中作為UI元件Model的 Managed-Bean和UI元件的耦合程度較高,仍适合由JSF管理其生命周期,不推薦放在Spring的IoC容器中。這一點和Struts及 WebWork2的Action不一樣,後兩者的Action要麼是唯一執行個體,要麼是對應每個請求的新執行個體,其生命周期遠沒有JSF的Managed- Bean那麼複雜。
  518. 有些時候,需要在JSF中手動擷取 Spring容器的Bean,讀者可能已經想到了,由于Spring的IoC容器是綁定在ServletContext上的,是以可以首先通過 FacesContext的getExternalContext()方法獲得ExternalContext執行個體,再通過 ExternalContext的getApplicationMap()方法獲得所有Application級别的Object,再通過查找名稱為 WebApplicationContext.ROOT_ WEB_APPLICATION_CONTEXT_ATTRIBUTE的對象就可以獲得Spring的IoC容器的執行個體,一旦獲得了Spring容器的執行個體,就可以擷取所有的Bean執行個體。
  519. 事實上,Spring已經提供了一個輔助類FacesContextUtils來擷取ApplicationContext執行個體。
  520. ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(
  521.     FacesContext.getCurrentInstance());
  522. 這樣,在JSF的代碼中,任何時候如果需要獲得ApplicationContext的執行個體,就可以按照上述方法調用。