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的实例,就可以按照上述方法调用。