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.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  66.     <bean id="userService" class="example.chapter7.UserServiceImpl" />
  67. </beans>
  68. 如果Spring的XML配置文件没有使用默认的文件名,就必须在Struts配置文件中声明Plugin时指定文件位置。
  69. <struts-config>
  70.     ...
  71.     <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
  72.         <set-property property="contextConfigLocation"
  73.             value="/WEB-INF/my-spring-config.xml"/>
  74.     </plug-in>
  75. </struts-config>
  76. 整个Struts_Spring1工程的结构如图7-56所示。
  77. 图7-56
  78. 编译工程,然后启动Resin服务器,可以看到效果和原始的Struts工程一样,但是,业务逻辑层组件和持久层组件(在本例中,为了简化问题,没有设计持久层组件)已被纳入Spring的IoC容器之中,从而清晰地将表示层和业务层划分开来。
  79. 使用这种方式集成Spring和Struts 时,如果合理地抽象出一个类似BaseAction的类作为所有Action的超类,在集成到Spring时,只需将BaseAction的超类从 Struts的Action类改为Spring提供的ActionSupport,然后在Struts中声明Spring的Plugin,就可以在不修改任何Action的情况下实现和Spring的集成。
  80. 第二种集成Struts的方案是将 Struts的所有Action都作为Spring IoC容器中的Bean来管理,就像Spring的Controller一样。然后通过Spring提供的 DelegatingRequestProcessor来替代Struts的默认派发器,以便让Spring能截获派发给Action的请求。在这种方式下,业务逻辑组件通过依赖注入的方式在Spring的IoC容器中就配置完毕了。
  81. 我们仍建立一个Struts_Spring2的工程,将Struts工程的所有文件复制过来,其目录结构和Struts_Spring1类似,如图7-57所示。
  82. 图7-57
  83. 为了能将业务组件UserService注入到每个Action类中,抽象出一个BaseAction是非常重要的,这样可以使代码的修改被限制在BaseAction中,而不会涉及每一个Action类。修改BaseAction如下。
  84. public class BaseAction extends Action {
  85.     private UserService userService;
  86.     public void setUserService(UserService userService) {
  87.         this.userService = userService;
  88.     }
  89.     public UserService getUserService() {
  90.         return userService;
  91.     }
  92. }
  93. 然后修改Struts的配置文件,添加Spring提供的DelegatingRequestProcessor和Plugin的声明。
  94. <struts-config>
  95.     ...
  96.     <controller processorClass="org.springframework.web.struts.Delegating RequestProcessor"/>
  97.     ...
  98.     <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn" />
  99. </struts-config>
  100. Spring 提供的Struts Plugin仍负责启动Spring容器,而DelegatingRequestProcessor可以让Spring接管请求,从而将请求派发到IoC 容器管理的Action实例。为此,在Spring的XML配置文件中,除了定义业务逻辑组件和其他组件外,还必须声明每一个Action类,其name 属性和Struts配置文件中的path要完全一致。由于我们在Struts配置文件中定义了两个Action:
  101. <action-mappings>
  102.     <action path="/login" type="example.chapter7.struts.LoginAction" ... />
  103.     <action path="/logout" type="example.chapter7.struts.LogoutAction" ... />
  104. </action-mappings>
  105. 因此,在Spring的配置文件中,除UserService组件外,还必须定义两个Action,并且要和Struts配置文件中的Action一一对应。
  106. <?xml version="1.0" encoding="UTF-8"?>
  107.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  108.     <bean id="userService" class="example.chapter7.UserServiceImpl" />
  109.     <bean name="/login" class="example.chapter7.struts.LoginAction">
  110.         <property name="userService" ref="userService" />
  111.     </bean>
  112.     <bean name="/logout" class="example.chapter7.struts.LogoutAction">
  113.         <property name="userService" ref="userService" />
  114.     </bean>
  115. </beans>
  116. 实际上,如果使用这种方案实现集成,Struts配置文件中Action的type属性将被忽略,因为Spring会根据path属性查找对应name的Action Bean。但是,仍然建议将type属性标识出来,便于查看URL和Action的关联。
  117. 使用这种方案时,由于Action被作为Bean纳入Spring的IoC容器管理,因此,可以获得完全的依赖注入能力。不过,最大的不便是所有的Action必须同时在Struts和Spring的配置文件中各配置一次,这增加了配置文件的维护成本。
  118. 在集成Struts时,选择何种方案需要根据实际情况决定。例如,由于RequestProcessor是Struts的一个扩展点,如果现有的Web应用程序已经扩展了 RequestProcessor,采用第一种方式就比较合适。不过,无论采用哪种方案,基于Struts的整体设计至关重要,如果没有统一的业务逻辑接口,而是将业务逻辑散落在各个Action中,则对代码的修改将涉及所有的Action类。
    7.6.2  集成WebWork2
  119. WebWork是一个非常简洁和优雅的Web 框架,WebWork的架构设计非常容易理解,它构建在一个命令模式的XWork框架之上,支持多种视图技术,并且WebWork也有一个丰富的标签库,能非常容易地实现校验。WebWork最大的特色就是使用IoC容器来管理所有的Action,并且拥有拦截器等一系列类似AOP的概念,因此, WebWork的整体设计比Struts更优秀,也更容易扩展。
  120. WebWork目前有两个主要版本: WebWork 1.x和WebWork 2.x,两个版本的差异较大,本节介绍的均以最新的WebWork 2.2为例。可以从WebWork的官方站点 http://www. opensymphony.com/webwork/下载最新的2.2.4版。
  121. 在WebWork 2.2版本之前,WebWork有一个自己的IoC容器,尽管仍可以和Spring集成,但是不那么方便。从WebWork 2.2开始,WebWork的设计者就推荐使用Spring的IoC容器来管理WebWork2的Action,这对于使用Spring框架的开发者来说无疑是一个好消息,因为这使得两者的集成更加容易,我们可以像对待Spring的Bean一样,在Spring的IoC容器中直接配置WebWork2的 Action。
  122. WebWork2的开发团队已经提供了一个 Spring和WebWork2的集成方案,正因为如此,在Spring框架中并没有WebWork2相关的支持包。如果读者在集成WebWork2时遇到问题,请首先参考WebWork官方网站( http://www.opensymphony.com/webwork/)的相关文档。
  123. 如果读者正在考虑使用WebWork2作为 MVC框架,则理所当然应当集成到Spring框架中。在下面的例子中,我们以最新的WebWork 2.2.4为例,首先创建一个基于WebWork2的Web应用程序,命名为WebWork2,其Eclipse工程结构如图7-58所示。
  124. 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应用程序的可测试性。
  125. 图7-58
  126. 我们假定读者对WebWork2已经有了一定的了解。在这个Web应用程序中,我们定义了两个Action,ListBooksAction用于列出所有的书籍,BookDetailAction用于查看书籍的详细信息,为此,我们还定义了一个BookService接口,并在所有Action的超类 BaseAction中定义了获取BookService接口的方法getBookService()。
  127. public abstract class BaseAction implements Action {
  128.     private static BookService bookService = new BookServiceImpl();
  129.     public BookService getBookService() {
  130.         return bookService;
  131.     }
  132. }
  133. 然后,在xwork.xml中配置好两个Action,并放到src目录下,这样,编译后该文件就位于/WEB-INF/classes目录下。
  134. <?xml version="1.0" encoding="UTF-8"?>
  135. <!DOCTYPE xwork
  136.           PUBLIC
  137.           "-//OpenSymphony Group//XWork 1.0//EN"
  138. <xwork>
  139.     <include file="webwork-default.xml"/>
  140.     <package name="default" extends="webwork-default">
  141.         <action name="listBooks" class="example.chapter7.webwork2.ListBooksAction">
  142.             <result name="success" type="dispatcher">/listBooks.jsp</result>
  143.         </action>
  144.         <action name="bookDetail" class="example.chapter7.webwork2. BookDetailAction">
  145.             <result name="success" type="dispatcher">/bookDetail.jsp</result>
  146.             <result name="error" type="dispatcher">/notFound.jsp</result>
  147.         </action>
  148.     </package>
  149. </xwork>
  150. 在web.xml中,我们声明WebWork2的ServletDispatcher,并映射所有以.action结尾的URL。
  151. <?xml version="1.0" encoding="UTF-8"?>
  152. <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" " http://java.sun.com/dtd/web-app_2_3.dtd">
  153. <web-app>
  154.     <servlet>
  155.         <servlet-name>dispatcher</servlet-name>
  156.         <servlet-class>
  157.             com.opensymphony.webwork.dispatcher.ServletDispatcher
  158.         </servlet-class>
  159.         <load-on-startup>0</load-on-startup>
  160.     </servlet>
  161.     <servlet-mapping>
  162.         <servlet-name>dispatcher</servlet-name>
  163.         <url-pattern>*.action</url-pattern>
  164.     </servlet-mapping>
  165.     <taglib>
  166.         <taglib-uri>webwork</taglib-uri>
  167.         <taglib-location>/WEB-INF/lib/webwork-2.2.4.jar</taglib-location>
  168.     </taglib>
  169. </web-app>
  170. 现在,作为一个独立的WebWork2的应用程序,我们已经可以在浏览器中看到实际效果了。启动Resin服务器,然后输入地址“http://localhost:8080/listBooks.action”,如图7-59所示。
  171. 我们还没有将Spring集成进来,并且和7.6.1节的Struts应用程序类似,获取BookService接口设计得不那么优雅。下一步,我们将WebWork2与Spring集成起来,让Spring来管理所有的Action和其他逻辑组件。
  172. 图7-59
  173. 我们将WebWork2工程复制一份,命名为WebWork2_Spring,然后开始一步一步地将其集成到Spring框架中。
  174. 在WebWork2中集成Spring是一件非常容易的事情。在WebWork2中,所有的Action也是由IoC容器管理的、默认地,WebWork2自身有一个IoC容器,负责管理所有的 Action,但是,正如前面提到的,从WebWork 2.2开始,WebWork2设计者推荐使用Spring的IoC容器来管理Action。只要对配置文件稍做修改,就可以让WebWork2直接使用 Spring的IoC容器。
  175. 第一步是编写一个webwork.properties的配置文件,放在src目录下,指定WebWork2使用的IoC容器名称。
  176. webwork.objectFactory = spring
  177. 该配置文件非常简单,只需要以上一行内容即可。更多的配置选项可以参考WebWork2的文档。编译后,该文件就会被放到/WEB-INF/classes/目录下。
  178. 现在,只要Spring的IoC容器启动了,WebWork2就会自动感知并使用Spring的IoC容器。要启动Spring的IoC容器,使用 ContextLoaderListener最合适不过,在web.xml中添加Spring的ContextLoaderListener声明。
  179. <listener>
  180.     <listener-class>
  181.         org.springframework.web.context.ContextLoaderListener
  182.     </listener-class>
  183. </listener>
  184. 下一步是在Spring默认的XML配置文件中定义所有的Bean,包括WebWork2的Action。由于使用 ContextLoaderListener来启动Spring的IoC容器,因此,默认的XML配置文件名称为 applicationContext.xml,内容如下。
  185. <?xml version="1.0" encoding="UTF-8"?>
  186.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  187. >
  188.     <bean id="bookService" class="example.chapter7.BookServiceImpl" />
  189.     <bean id="listBooksAction" class="example.chapter7.webwork2. ListBooks Action" scope="prototype">
  190.         <property name="bookService" ref="bookService" />
  191.     </bean>
  192.     <bean id="bookDetailAction" class="example.chapter7.webwork2. BookDetail Action" scope="prototype">
  193.         <property name="bookService" ref="bookService" />
  194.     </bean>
  195. </beans>
  196. 要特别注意的是,WebWork2使用的Action和Struts不同,对于每个用户请求,WebWork2都会创建一个新的Action实例来处理用户请求,因此,必须将Action的scope定义为prototype,使得每次WebWork2向Spring IoC容器请求一个Action时,Spring都能返回一个新的实例。采用Bean模板也不失为一个好办法,具体配置请参考第3章3.9.2“使用模板装配”一节。
  197. 最后一步是修改xwork.xml,将每个Action的class属性从类名改为Spring中对应的id,这样,WebWork2就会根据此id向Spring的IoC容器请求一个新的Action实例。
  198. <?xml version="1.0" encoding="UTF-8"?>
  199. <!DOCTYPE xwork
  200.           PUBLIC
  201.           "-//OpenSymphony Group//XWork 1.0//EN"
  202. <xwork>
  203.     <include file="webwork-default.xml"/>
  204.     <package name="default" extends="webwork-default">
  205.         <action name="listBooks" class="listBooksAction">
  206.             <result name="success" type="dispatcher">/listBooks.jsp</result>
  207.         </action>
  208.         <action name="bookDetail" class="bookDetailAction">
  209.             <result name="success" type="dispatcher">/bookDetail.jsp</result>
  210.             <result name="error" type="dispatcher">/notFound.jsp</result>
  211.         </action>
  212.     </package>
  213. </xwork>
  214. 完成了以上步骤后,将spring.jar放入/WEB-INF/lib目录下,然后启动Resion服务器,就可以看到如下输出。
  215. [2006/12/06 16:41:43.138] Initializing WebWork-Spring integration...
  216. [2006/12/06 16:41:43.154] Setting autowire strategy to name
  217. [2006/12/06 16:41:43.154] ... initialized WebWork-Spring integration successfully
  218. 打开浏览器测试,其结果与前面WebWork2工程的结果完全相同,不过,所有的Action和逻辑组件都由Spring的IoC来管理,这使得应用程序的层次更加清晰。WebWork2_Spring工程的整个结构如图7-60所示。
  219. 图7-60
  220. 默认情况下,在使用Spring的IoC容器时,WebWork2使用byName的自动装配功能来装配WebWork2的Action。可以根据需要将其修改为byType。如果Action较多,更好的配置方法是首先定义一个模板Bean,作为所有Action Bean的模板。
  221. <bean id="webworkActionTemplate" abstract="true" scope="prototype">
  222.     <property name="service1" ref="service1" />
  223.     <property name="service2" ref="service2" />
  224. </bean>
  225. 其余的Action Bean通过继承此模板Bean就可以安全地获得scope设定和其他依赖注入的属性。
    7.6.3  集成Tiles
  226. 前面我们讲到了JavaEE Web组件处理请求的3种方式:转发(Forward)、包含(Include)和错误(Error),Tiles框架将Include方式发挥到了极致。Tiles并不是一个MVC框架,从本质上说,Tiles是一个模板框架,它将页面分成几个小的部分,然后动态地将它们组合在一起,从而允许更灵活地创建可以重用的页面组件。
  227. 虽然Tiles是Struts框架的一部分,但是Tiles从一开始就被设计为能够在Struts外独立适用。事实上,Tiles可以用于任何Web框架,当然也包括Spring的MVC框架。
  228. Spring已经对Tiles做了非常好的集成,在Spring框架中使用Tiles更加方便。下面的例子我们创建了一个Spring_Tiles工程,结构如图7-61所示。
  229. 图7-61
  230. 将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。
  231. <?xml version="1.0" encoding="UTF-8"?>
  232. <!DOCTYPE web-app PUBLIC
  233.     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  234. <web-app>
  235.     <servlet>
  236.         <servlet-name>dispatcher</servlet-name>
  237.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</ servlet-class>
  238.         <load-on-startup>0</load-on-startup>
  239.     </servlet>
  240.     <servlet-mapping>
  241.         <servlet-name>dispatcher</servlet-name>
  242.         <url-pattern>*.do</url-pattern>
  243.     </servlet-mapping>
  244.     <taglib>
  245.         <taglib-uri>http://struts.apache.org/tags-tiles</taglib-uri>
  246.         <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
  247.     </taglib>
  248. </web-app>
  249. 我们先来看看如何在Tiles中组合出一个页面。在这个Web应用程序中,我们一共设计了两个页面,一个是登录页面,一个是欢迎页面,每个页面都被分为页眉(header)、主体(body)和页脚(footer)三部分。为了能在Tiles中组合出一个完整的页面,我们分别编写header.jsp作为页眉,footer.jsp作为页脚,在登录页中,主体部分是login.jsp,而在欢迎页中,主体是welcome.jsp,如图7-62所示。
  250. 通过将页面拆为几个部分,我们就可以复用页面的公共部分(如header.jsp和footer.jsp),在Tiles中,每个可重用的部分被称为一个可视化组件,通常是一个JSP文件,然后用一个模板将几个组件组合到一起,就构成了一个完整的页面。例如,template.jsp将页眉、主体和页脚3个组件组合在一起,作为一个完整的页面展示给用户。
  251. <%@ page contentType="text/html; charset=utf-8" %>
  252. <%@ taglib prefix="tiles" uri=" http://struts.apache.org/tags-tiles" %>
  253. <html>
  254. <head>
  255. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  256. <title><tiles:getAsString name="title" /></title>
  257. </head>
  258. <body>
  259. <table width="100%" border="0" cellspacing="0" cellpadding="0">
  260.     <tr><td><tiles:insert name="header" /></td></tr>
  261.     <tr><td><tiles:insert name="body" /></td></tr>
  262.     <tr><td><tiles:insert name="footer" /></td></tr>
  263. </table>
  264. </body>
  265. </html>
  266. 组合tile非常直观,用<tiles:insert>标签就可以嵌入一个tile,用<tiles:getAsString>可以将一个属性写到页面中。在template.jsp模板里,一共插入了3个组件和1个属性值作为标题。
  267. 在template.jsp 中,我们只定义了每个组件的名称,并没有指定具体的jsp文件。在Tiles中,每个页面的布局都被定义在XML配置文件中,我们将Tiles的配置文件命名为tiles-defs.xml,并放到/web/WEB-INF/目录下,在这个配置文件中,我们定义了login和welcome两个完整的页面。
  268. <?xml version="1.0" encoding="UTF-8"?>
  269. <!DOCTYPE tiles-definitions PUBLIC
  270.        "-//Apache Software Foundation//DTD Tiles Configuration 1.3//EN"
  271. <tiles-definitions>
  272.     <definition name="default" path="/template.jsp">
  273.         <put name="title"  value="Default Title" />
  274.         <put name="header" value="/header.jsp" />
  275.         <put name="footer" value="/footer.jsp" />
  276.     </definition>
  277.     <definition name="login" extends="default" >
  278.         <put name="title" value="Please login" />
  279.         <put name="body"  value="/login.jsp" />
  280.     </definition>
  281.     <definition name="welcome" extends="default">
  282.         <put name="title" value="Welcome!" />
  283.         <put name="body"  value="/welcome.jsp" />
  284.     </definition>
  285. </tiles-definitions>
  286. 每个 <definition>定义一个完整的页面,在Tiles中,页面是可以继承的,例如,上述default页面定义的header和 footer分别为header.jsp和footer.jsp,并设置页面的title属性为“Default Title”,login页面就可以从default继承,并定义body为login.jsp,然后覆盖了title属性。通过继承,就使得页面定义更加简洁。
  287. 最后一步是在Spring的XML配置文件中配置Tiles框架,并选择一个合适的ViewResolver,让它能解析Tiles视图。
  288. <bean id="tilesConfig" class="org.springframework.web.servlet.view. tiles.TilesConfigurer">
  289.     <property name="definitions">
  290.         <list>
  291.             <value>/WEB-INF/tiles-defs.xml</value>
  292.         </list>
  293.     </property>
  294. </bean>
  295. <bean id="viewResolver" class="org.springframework.web.servlet.view. InternalResourceViewResolver">
  296.     <property name="viewClass" value="org.springframework.web.servlet.view. tiles.TilesView" />
  297. </bean>
  298. TilesConfigurer只需要指定Tiles配置文件就可以自动地配置好Tiles,为了让视图解析器能识别TilesView,使用InternalResourceViewResolver并将TilesView绑定即可。
  299. LoginController和 WelcomeController的编写和前面的完全相同,在LoginController中,返回的视图名称是“login”,而 WelcomeController返回的视图名称是“welcome”,细心的读者可能已经发现了,视图解析器的任务就是在Tiles配置文件中查找对应名称的页面,然后根据页面的定义,由TilesView将整个页面渲染出来。两个页面的效果如图7-63和图7-64所示。
  300.           
  301. 图7-63                                     图7-64
    7.6.4  集成JSF
  302. JSF是JavaServer Faces的缩写,JSF是JavaEE中构建Web应用程序的又一个全新的MVC框架。与其他常见的MVC框架相比,JSF提供了一种以组件为中心的方式来开发Web应用程序。JSF的目标是提供一种类似ASP.Net的可视化Web组件,开发人员只需要将已有的JSF组件拖放到页面上,就可以迅速建立一个Web应用程序,而不必从头开始编写UI界面。因此,JSF的易用性很大程度上取决于一个简单、高效的可视化开发环境。
  303. 和传统的MVC框架(如Struts)相比, JSF更像是一个Web版本的Swing应用程序。传统的MVC框架的View是以页面为中心的,而JSF的View则是一系列可复用的UI组件。因此, JSF应用程序的生命周期更为复杂。一般来说,JSF处理用户请求的流程如图7-65所示。
  304. 除了恢复视图和渲染响应是必须的之外,其他步骤都可以视情况跳过。例如,在验证失败后,就直接跳到渲染响应,将错误信息展示给用户。
  305. JSF的真正威力在于它的UI界面组件。与 ASP.NET类似,JSF的UI组件使开发人员能够使用已创建好的UI组件(如JSF内置的UI组件)来创建Web页面,而非完全从头创建,从而提供了前所未有的开发效率。JSF的UI组件可以是简单到只是显示文本的outputLabel,或者复杂到可以表示来自数据库表的表格。
  306. 此外,JSF也使用类似Spring IoC容器的方式来管理Model,即与UI组件绑定的Bean,在JSF中称为Managed-Bean,并且也支持依赖注入。Managed- Bean的生命周期可以是request、session和application,分别对应一次请求、一次会话和整个Web应用程序生存期。
  307. 在开发JSF应用之前,我们需要首先准备开发环境。由于JSF也是一个规范,不同的厂商都可以有自己的实现。这里我们采用的是JSF 1.1规范(JSR 127),因为JSF 1.1仅需要Servlet 2.3规范的支持,在大多数Web服务器上都能正常运行,而最新的JSF 1.2则要求Servlet 2.5规范,目前除了少数服务器外,大多数服务器还无法支持。
  308. 我们还需要一个JSF 1.1的实现。SUN给出了一个JSF 1.1的参考实现,可以用于开发,以保证我们的JSF应用程序将来可以移植到其他的JSF实现。Apache也提供了一个MyFaces的JSF实现,读者可以参考Apache的官方站点获取详细信息,本书不对此做更多讨论。
  309. 下面的例子改自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所示。
  310. 图7-66
  311. 编译工程只需要引用jsf-api.jar。几个主要的接口和类如下。
  312. Service接口是唯一的业务逻辑接口,它仅定义了一个subscribe方法。
  313. public interface Service {
  314.     void subscribe(SubscriberBean subscriber);
  315. }
  316. 其实现类ServiceImpl仅仅简单地打印出用户的订阅信息。
  317. public class ServiceImpl implements Service {
  318.     public void subscribe(SubscriberBean subscriber) {
  319.         System.out.println("User /"" + subscriber.getName()
  320.                 + "/" with email /"" + subscriber.getEmail()
  321.                 + "/" subscribed successfully with preferred language "
  322.                 + subscriber.getLanguage());
  323.     }
  324. }
  325. SubscriberBean 是一个与前端UI绑定的Session范围的Managed-Bean,其作用范围是Session,在SubscriberBean中还定义了 submit()方法来处理JSF的Action,因此,在SubscriberBean中必须要注入一个Service对象,才能完成实际业务方法的调用。
  326. public class SubscriberBean {
  327.     private String name;
  328.     private String email;
  329.     private String language;
  330.     private Service service = new ServiceImpl();
  331.     public void setService(Service service) {
  332.         this.service = service;
  333.     }
  334.     public String getName() { return name; }
  335.     public void setName(String name) { this.name = name; }
  336.     public String getEmail() { return email; }
  337.     public void setEmail(String email) { this.email = email; }
  338.     public String getLanguage() { return language; }
  339.     public void setLanguage(String language) { this.language = language; }
  340.     public String submit() {
  341.         // 处理订阅请求:
  342.         service.subscribe(this);
  343.         return "success";
  344.     }
  345. }
  346. EmailValidator是一个自定义的JSF验证器,读者可以参考JSF相关文档获得有关验证器的使用方法,这里仅给出实现。
  347. public class EmailValidator implements Validator {
  348.     public void validate(FacesContext context, UIComponent component, Object value)
  349.             throws ValidatorException {
  350.         String email = ((String)value).trim();
  351.         if(!email.matches("[a-zA-Z0-9][//w//.//-]*@[a-zA-Z0-9][//w//.//-] *//.[a-zA-Z][a-zA-Z//.]*")) {
  352.             throw new ValidatorException(
  353.                     new FacesMessage("Invalid email address"));
  354.         }
  355.     }
  356. }
  357. 下面我们需要配置JSF,首先,在web.xml中声明JSF相关Listener和FacesServlet。
  358. <?xml version="1.0" encoding="UTF-8"?>
  359. <!DOCTYPE web-app PUBLIC
  360.   "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  361. <web-app>
  362.     <context-param>
  363.         <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
  364.         <param-value>client</param-value>
  365.     </context-param>
  366.     <context-param>
  367.         <param-name>com.sun.faces.validateXml</param-name>
  368.         <param-value>true</param-value>
  369.     </context-param>
  370.     <listener>
  371.         <listener-class>
  372.             com.sun.faces.config.ConfigureListener
  373.         </listener-class>
  374.     </listener>
  375.     <!-- Faces Servlet -->
  376.     <servlet>
  377.         <servlet-name>Faces Servlet</servlet-name>
  378.         <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
  379.         <load-on-startup>0</load-on-startup>
  380.     </servlet>
  381.     <servlet-mapping>
  382.         <servlet-name>Faces Servlet</servlet-name>
  383.         <url-pattern>*.faces</url-pattern>
  384.     </servlet-mapping>
  385. </web-app>
  386. FacesServlet 是整个JSF应用程序的前端入口,负责接收所有的用户请求。然后,需要编写一个默认的faces-config.xml配置文件,告诉JSF所有的配置信息,包括自定义的验证器、导航规则、所有的Managed-Bean和这些Bean之间的依赖关系。将faces-config.xml放到/WEB- INF/目录下。
  387. <?xml version="1.0" encoding="UTF-8"?>
  388. <!DOCTYPE faces-config PUBLIC
  389.   "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  390. <faces-config>
  391.     <!-- 声明自定义的Validator -->
  392.     <validator>
  393.         <validator-id>emailValidator</validator-id>
  394.         <validator-class>example.chapter7.EmailValidator</validator-class>
  395.     </validator>
  396.     <!-- 声明所有的Managed Bean -->
  397.     <managed-bean>
  398.         <managed-bean-name>service</managed-bean-name>
  399.         <managed-bean-class>example.chapter7.ServiceImpl</managed-bean-class>
  400.         <managed-bean-scope>application</managed-bean-scope>
  401.     </managed-bean>
  402.     <managed-bean>
  403.         <managed-bean-name>subscriber</managed-bean-name>
  404.         <managed-bean-class>example.chapter7.SubscriberBean</managed-bean-class>
  405.         <managed-bean-scope>session</managed-bean-scope>
  406.         <managed-property>
  407.             <property-name>service</property-name>
  408.             <value>#{service}</value>
  409.         </managed-property>
  410.     </managed-bean>
  411.     <!-- 定义导航规则 -->
  412.     <navigation-rule>
  413.         <from-view-id>/index.jsp</from-view-id>
  414.         <navigation-case>
  415.             <from-outcome>success</from-outcome>
  416.             <to-view-id>/thanks.jsp</to-view-id>
  417.         </navigation-case>
  418.     </navigation-rule>
  419. </faces-config>
  420. 下一步是编写两个JSP页面,使用JSF标准的标签库,读者可以发现JSF使用的表达式和Velocity非常类似,不过将“$”改为了“#”。index.jsp负责接受用户输入并验证表单。
  421. < %@page contentType="text/html;charset=UTF-8" %>
  422. < %@taglib uri=" http://java.sun.com/jsf/core" prefix="f" %>
  423. < %@taglib uri=" http://java.sun.com/jsf/html" prefix="h" %>
  424. <html>
  425. <head>
  426. <title>Subscribe Form</title>
  427. </head>
  428. <body>
  429.     <f:view>
  430.         <h:form>
  431.             <h4>Subscribe</h4>
  432.             Your name:
  433.             <h:inputText id="id_name" value="#{subscriber.name}" required="true">
  434.                 <f:validateLength minimum="3" maximum="20" />
  435.             </h:inputText>
  436.             <h:message for="id_name" />
  437.             <br/>
  438.             Your email:
  439.             <h:inputText id="id_email" value="#{subscriber.email}" required="true">
  440.                 <f:validator validatorId="emailValidator" />
  441.             </h:inputText>
  442.             <h:message for="id_email" />
  443.             <br/>
  444.             Preferred Language:
  445.             <h:selectOneMenu value="#{subscriber.language}" required="true">
  446.                 <f:selectItem itemLabel="English" itemValue="English" />
  447.                 <f:selectItem itemLabel="Chinese" itemValue="Chinese" />
  448.             </h:selectOneMenu>
  449.             <br/>
  450.             <h:commandButton type="submit" value="Submit" action="#{subscriber. submit}" />
  451.         </h:form>
  452.     </f:view>
  453. </body>
  454. </html>
  455. thanks.jsp用于提示用户订阅成功。
  456. < %@page contentType="text/html;charset=UTF-8" %>
  457. < %@taglib uri=" http://java.sun.com/jsf/core" prefix="f" %>
  458. < %@taglib uri=" http://java.sun.com/jsf/html" prefix="h" %>
  459. <html>
  460. <head><title>Thank you</title></head>
  461. <body>
  462.     <f:view>
  463.         <h3>
  464.             Thank you, <h:outputText value="#{subscriber.name}"/>!
  465.         </h3>
  466.         A confirm mail has been sent to your mail box <h:outputText value="#{subscriber.email}" />.
  467.     </f:view>
  468. </body>
  469. </html>
  470. 编译工程后,启动Resin服务器,就可以直接在浏览器中输入 http://localhost:8080/ index.faces,其执行效果如图7-66和图7-67所示。
  471.        
  472. 图7-66                                     图7-67
  473. 现在,我们来考虑如何将JSF集成到 Spring框架中。我们注意到,在上面的配置中,Service对象是由JSF定义为全局变量并注入到SubscriberBean中的,我们更希望能从Spring的IoC容器中获得Service对象,然后将其注入到JSF的SubscriberBean中,这样使得JSF仅作为Web层与用户打交道,真正的中间层逻辑和后端持久层的Bean都交给Spring管理,使整个应用程序的层次更加清晰。
  474. 在Spring中集成JSF非常简单,其关键在于声明Spring提供的一个Delegating VariableResolver,在faces-config.xml中添加下列内容。
  475. <faces-config>
  476.     <application>
  477.         <variable-resolver>
  478.             org.springframework.web.jsf.DelegatingVariableResolver
  479.         </variable-resolver>
  480.     </application>
  481.     ...
  482. </faces-config>
  483. 我们看看JSF是如何实现依赖注入的。在前面的faces-config.xml中,SubscriberBean需要注入一个Service对象,我们是这么注入的。
  484. <managed-property>
  485.     <property-name>service</property-name>
  486.     <value>#{service}</value>
  487. </managed-property>
  488. JSF 根据名称#{service}去查找名称为service的Managed-Bean,然后将其注入到SubscriberBean中。如果我们声明了 Spring的DelegatingVariableResolver,则由DelegatingVariableResolver负责查找这个名称为 service的Bean,DelegatingVariable Resolver就有机会从Spring的IoC容器中获得名称为service的Bean,然后交给JSF,JSF再将其注入到 SubscriberBean中,图7-68很好地说明了DelegatingVariableResolver实现的功能。
  489. 然后,删除faces-config.xml中定义的Service Bean,因为这个Bean已被放入Spring容器中管理。在Spring的applicationContext.xml中定义这个Service Bean。
  490. <?xml version="1.0" encoding="UTF-8"?>
  491.        xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance"
  492. >
  493.     <bean id="service" class="example.chapter7.ServiceImpl" />
  494. </beans>
  495. 最后一步是在web.xml中通过声明Spring提供的ContextLoaderListener来启动Spring容器,注意:该Listener应当在其他Listener之前定义,以保证Spring容器首先被启动。
  496. <listener>
  497.     <listener-class>
  498.         org.springframework.web.context.ContextLoaderListener
  499.     </listener-class>
  500. </listener>
  501. 集成后的整个工程结构如图7-69所示。
  502. 无需编译,重新启动Resin服务器后,可以看到和Spring集成的JSF应用的运行效果与前面的JSF应用一致,不同之处在于Service组件从JSF容器移动到Spring容器内了。
  503. 需要注意的几点是, DelegatingVariableResolver将首先试图查找faces-config.xml中定义的Managed-Bean,找不到才在 Spring的IoC容器中查找。因此,Spring容器中定义的Bean和JSF中定义的Managed-Bean千万不要有相同的名称,以免造成冲突。
  504. 图7-69
  505. 另一个值得注意的地方是,作为中间逻辑组件和后端持久化组件的Bean非常适合放在Spring的IoC容器中,以便获得AOP、声明式事务等强大的支持。但是,在JSF中作为UI组件Model的 Managed-Bean和UI组件的耦合程度较高,仍适合由JSF管理其生命周期,不推荐放在Spring的IoC容器中。这一点和Struts及 WebWork2的Action不一样,后两者的Action要么是唯一实例,要么是对应每个请求的新实例,其生命周期远没有JSF的Managed- Bean那么复杂。
  506. 有些时候,需要在JSF中手动获取 Spring容器的Bean,读者可能已经想到了,由于Spring的IoC容器是绑定在ServletContext上的,因此可以首先通过 FacesContext的getExternalContext()方法获得ExternalContext实例,再通过 ExternalContext的getApplicationMap()方法获得所有Application级别的Object,再通过查找名称为 WebApplicationContext.ROOT_ WEB_APPLICATION_CONTEXT_ATTRIBUTE的对象就可以获得Spring的IoC容器的实例,一旦获得了Spring容器的实例,就可以获取所有的Bean实例。
  507. 事实上,Spring已经提供了一个辅助类FacesContextUtils来获取ApplicationContext实例。
  508. ApplicationContext ctx = FacesContextUtils.getWebApplicationContext(
  509.     FacesContext.getCurrentInstance());
  510. 这样,在JSF的代码中,任何时候如果需要获得ApplicationContext的实例,就可以按照上述方法调用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值