posts - 495,  comments - 11,  trackbacks - 0

6.4 Spring整合Struts

虽然Spring也提供了自己的MVC组件,但一来Spring的MVC组件过于繁琐,二     来Struts的拥护者实在太多。因此,很多项目都会选择使用Spring整合Struts框架。而且Spring确实可以无缝整合Struts框架,二者结合成一个更实际的J2EE开发平台。

6.4.1 利用Struts的PlugIn来启动Spring容器

使用Spring的Web应用时,不用手动创建Spring容器,而是通过配置文件声明式地创建Spring容器。因此,在Web应用中创建Spring容器有如下两个方式:

   ● 直接在web.xml文件中配置创建Spring容器。

   ● 利用第三方MVC框架的扩展点,创建Spring容器。

其实第一种创建Spring容器的方式更加常见。为了让Spring容器随Web应用的启动而自动启动,有如下两个方法:

   ● 利用ServletContextListener实现。

   ● 采用load-on-startup Servlet实现。

Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类可以作为Listener使用,会在创建时自动查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,只需在web.xml文件中增加如下配置片段即可:

<listener>

   <listener-class>org.springframework.web.context.
    ContextLoaderListener</listener-class>

</listener>

如果有多个配置文件需要载入,则考虑使用<context-param>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contextConfigLocation的参数。因此,配置context-param时,参数名字应该是contextConfigLocation。

带多个配置文件的web.xml文件如下:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Web配置文件的根元素,以及相应的Schema信息 -->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee   
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    version="2.4">

    <!-- 确定多个配置文件 -->

    <context-param>

        <!-- 参数名为contextConfigLocation -->

        <param-name>contextConfigLocation</param-name>

        <!-- 多个配置文件之间以“,”隔开 -->

        <param-value>/WEB-INF/daoContext.xml,/WEB-INF/
        applicationContext.xml</param-value>

    </context-param>

    <!-- 采用listener创建ApplicationContext实例 -->

    <listener>

        <listener-class>org.springframework.web.context.
        ContextLoaderListener</listener-class>

    </listener>

</web-app>

如果没有通过contextConfigLocation指定配置文件,Spring会自动查找application- Context.xml配置文件;如果有contextConfigLocation,则利用该参数确定的配置文件。如果无法找到合适的配置文件,Spring将无法正常初始化。

Spring根据bean定义创建WebApplicationContext对象,并将其保存在web应用的ServletContext中。大部分情况下,应用中的Bean无须感受到ApplicationContext的存在,只要利用ApplicationContext的IoC即可。

如果需要在应用中获取ApplicationContext实例,可以通过如下代码获取:

//获取当前Web应用的Spring容器

WebApplicationContext ctx =

    WebApplicationContextUtils.getWebApplicationContext(servletContext);

除此之外,Spring提供了一个特殊的Servlet类ContextLoaderServlet。该Servlet在启动时,会自动查找WEB-INF/下的applicationContext.xml文件。

当然,为了让ContextLoaderServlet随应用的启动而启动,应将此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一点比较合适,这样可以保证Application- Context更快的初始化。

如果只有一个配置文件,并且文件名为applicationContext.xml,在web.xml文件中增加如下一段即可:

<servlet>

    <servlet-name>context</servlet-name>

    <servlet-class>org.springframework.web.context.ContextLoaderServlet
    </servlet-class>

    <load-on-startup>1</load-on-startup>

</servlet>

该Servlet用于提供“后台”服务,主要用于创建Spring容器,无须响应客户请求,因此无须配置servlet-mapping。

如果有多个配置文件,一样使用<context-param>元素来确定多个配置文件。

事实上,不管是ContextLoaderServlet,还是ContextLoaderListener,都依赖于ContextLoader创建ApplicationContext实例。

在ContextLoader代码的第240行,有如下代码:

String configLocation = servletContext.getInitParameter
(CONFIG_LOCATION_PARAM);

if (configLocation != null) {

    wac.setConfigLocations(StringUtils.tokenizeToStringArray
    (configLocation,

    ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));

}

其中,CONFIG_LOCATION_PARAM是该类的常量,其值为contextConfigLocation。可以看出,ContextLoader首先检查servletContext中是否有contextConfigLocation的参数,如果有该参数,则加载该参数指定的配置文件。

ContextLoaderServlet与ContextLoaderListener底层都依赖于ContextLoader。因此,二者的效果几乎没有区别。之间的区别不是它们本身引起的,而是由于Servlet规范,Listener比Servlet优先加载。因此,采用ContextLoaderListener创建ApplicationContext的时机更早。

当然,也可以通过ServletContext的getAttribute方法获取ApplicationContext。但使用WebApplicationContextUtils类更便捷,因为无须记住ApplicationContext的属性名。即使ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE属性没有对应对象,WebApplicationContextUtils的getWebApplicationContext()方法将会返回空,而不会引起异常。

到底需要使用Listener,还是使用load-on-startup Servlet来创建Spring容器呢?通常推荐使用Listener来创建Spring容器。但Listerner是Servlet 2.3以上才支持的标准,因此,必须Web容器支持Listener才可使用Listerner。

注意:使用Listener创建Spring容器之前,应先评估Web容器是否支持Listener标准。

还有一种情况,利用第三方MVC框架的扩展点来创建Spring容器,比如Struts。在第2章介绍Strust框架时,知道Struts有一个扩展点PlugIn。

实际上,Spring正是利用了PlugIn这个扩展点,从而提供与Struts的整合。Spring提供了PlugIn接口的实现类org.springframework.web.struts.ContextLoaderPlugIn。这个实现类可作为Struts的PlugIn配置,Struts框架启动时,将自动创建Spring容器。

为了利用Struts的PlugIn创建Spring容器,只需在Struts配置文件中增加如下片段   即可:

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">

<set-property property="contextConfigLocation"

      value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.
      xml"/>

</plug-in>

其中,指定contextConfigLocation属性值时,即可以指定一个Spring配置文件的位置,可以指定多个Spring配置文件的位置。

6.4.2 MVC框架与Spring整合的思考

对于一个基于B/S架构的J2EE应用而言,用户请求总是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。此时有一个问题,控制器应该如何获得业务逻辑组件?

最容易想到的策略是,直接通过new关键字创建业务逻辑组件,然后调用业务逻辑组件的方法,根据业务逻辑方法的返回值确定结果。

实际的应用中,很少见到采用上面的访问策略,因为这是一种非常差的策略。不这样做至少有如下3个原因:

   ● 控制器直接创建业务逻辑组件,导致控制器和业务逻辑组件的耦合降低到代码层次,不利于高层次解耦。

   ● 控制器不应该负责业务逻辑组件的创建,控制器只是业务逻辑组件的使用者。无须关心业务逻辑组件的实现。

   ● 每次创建新的业务逻辑组件将导致性能下降。

答案是采用工厂模式或服务定位器。采用服务定位器的模式,是远程访问的场景。在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无须理会该业务逻辑组件的创建,直接调用即可,但在调用之前,必须先找到该服务——这就是服务定位器的概念。经典J2EE应用就是这种结构的应用。

对于轻量级的J2EE应用,工厂模式则是更实际的策略。因为轻量级的J2EE应用里,业务逻辑组件不是EJB,通常就是一个POJO,业务逻辑组件的生成通常由工厂负责,而且工厂可以保证该组件的实例只需一个就够了,可以避免重复实例化造成的系统开销。

如图6.2就是采用工厂模式的顺序图。

图6.2 工厂模式顺序图

采用工厂模式,将控制器与业务逻辑组件的实现分离,从而提供更好的解耦。

在采用工厂模式的访问策略中,所有的业务逻辑组件的创建由工厂负责,业务逻辑组件的运行也由工厂负责。而控制器只需定位工厂实例即可。

如果系统采用Spring框架,则Spring成为最大的工厂。Spring负责业务逻辑组件的创建和生成,并可管理业务逻辑组件的生命周期。可以如此理解,Spring是一个性能非常优秀的工厂,可以生产出所有的实例,从业务逻辑组件,到持久层组件,甚至控制器。

现在的问题是,控制器如何访问到Spring容器中的业务逻辑组件?为了让Action访 问Spring的业务逻辑组件,有两种策略:

   ● Spring管理控制器,并利用依赖注入为控制器注入业务逻辑组件。

   ● 控制器显式定位Spring工厂,也就是Spring的容器ApplicationContext实例,并从工厂中获取业务逻辑组件实例的引用。

第一种策略,充分利用Spring的IoC特性,是最优秀的解耦策略。但不可避免带来一些不足之处,归纳起来主要有如下不足之处:

   ● Spring管理Action,必须将所有的Action配置在Spring容器中,而struts-config.xml文件中的配置也不会减少,导致配置文件大量增加。

   ● Action的业务逻辑组件接收容器注入,将导致代码的可读性降低。

总体而言,这种整合策略是利大于弊。

第二种策略,与前面介绍的工厂模式并没有太大的不同。区别是Spring容器充当了业务逻辑组件的工厂。控制器负责定位Spring容器,通常Spring容器访问容器中的业务逻辑组件。这种策略是一种折衷,降低了解耦,但提高了程序的可读性。

Spring完全支持这两种策略,既可以让Spring容器管理控制器,也可以让控制器显式定位Spring容器中的业务逻辑组件。

6.4.3 使用DelegatingRequestProcessor

这里介绍的是第一种整合策略:让Spring管理Struts的Action。那么同样有一个问题,让Spring管理Struts的Action时,客户端的HTTP 请求如何转向Spring容器中的Action?

当使用Struts作为MVC框架时,客户端的HTTP请求都是直接向ActionServlet请求的,因此关键就是让ActionServlet将请求转发给Spring容器中的Action。这很明显可以利用Spring的另一个扩展点:通过扩展RequestProcessor完成,使用扩展的RequestProcessor替换Struts的RequestProcessor。

Spring完成了这种扩展,Spring提供的DelegatingRequestProcessor继承Request- Processor。为了让Struts使用DelegatingRequestProcessor,还需要在struts-config.xml文件中增加如下一行:

//使用spring的RequestProcessor替换struts原有的RequestProcessor

<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>

完成这个设置后,Struts会将截获到的用户请求转发到Spring context下的bean,根据bean的name属性来匹配。而Struts中的action配置则无须配置class属性,即使配置了class属性也没有任何用处,即下面两行配置是完全一样的:

//配置struts action时,指定了实现类

<action path="/user" type="lee.UserAction"/>

//配置struts action时,没有指定实现类

<action path="/user"/>

下面的示例程序在上一个示例程序的基础上稍作修改,增加了客户端验证和程序国际化部分。也调用了Spring的业务bean来验证登录。先看修改后的struts-config.xml文件:

<!-- XML文件版本,编码集 -->

<?xml version="1.0" encoding="gb2312"?>

<!-- Struts配置文件的文件头,包括DTD等信息 -->

<!DOCTYPE struts-config PUBLIC

          "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

          "http://struts.apache.org/dtds/struts-config_1_2.dtd">

<!-- struts配置文件的根元素 -->

<struts-config>

    <!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->

    <form-beans>

        <!-- 定义了一个formbean,确定formbean名和实现类 -->

        <form-bean name="loginForm" type="lee.LoginForm"/>

    </form-beans>

    <!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->

    <action-mappings>

        <!-- 这里只定义了一个action。而且没有指定该action的type元素 -->

        <action path="/login" name="loginForm"

            scope="request" validate="true" input="/login.jsp" >

            <!-- 定义action内的两个局部forward元素 -->

            <forward name="input" path="/login.jsp"/>

            <forward name="welcome" path="/welcome.html"/>

        </action>

    </action-mappings>

    <!-- 使用DelegatingRequestProcessor替换RequestProcessor -->

    <controller processorClass="org.springframework.web.struts.
    DelegatingRequestProcessor"/>

    <!-- 加载国际化的资源包 -->

    <message-resources parameter="mess"/>

    <!-- 装载验证的资源文件 -->

    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">

        <set-property property="pathnames" value="/WEB-INF/validator-
        rules.xml,/WEB-INF/validation.xml" />

        <set-property property="stopOnFirstError" value="true"/>

    </plug-in>

    <!-- 装载Spring配置文件,随应用的启动创建ApplicationContext实例 -->

    <plug-in className="org.springframework.web.struts.
    ContextLoaderPlugIn">

        <set-property property="contextConfigLocation"

            value="/WEB-INF/applicationContext.xml,

                   /WEB-INF/action-servlet.xml"/>

    </plug-in>

</struts-config>

修改后的struts-config.xml文件,增加加载国际化资源文件。配置Struts的action不需要class属性,完成了ApplicationContext的创建。

然后考虑web.xml文件的配置,在web.xml文件中必须配置Struts框架的加载。除此之外,因为使用了Spring管理Struts的Action,而Action是随HTTP请求启动的,因此,应将Action的作用域配置成Request,为了使用Request作用域,必须在web.xml文件中增加适当的配置。

下面是web.xml文件的代码:

<?xml version="1.0" encoding="GBK"?>

<!-- 指定Web配置文件的根元素,以及对应的Schema信息 -->

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

    version="2.4">

    <!-- 定义一个Filter,该Filter是使用Request作用域的基础 -->

    <filter>

        <filter-name>requestContextFilter</filter-name>

        <filter-class>org.springframework.web.filter.
        RequestContextFilter </filter-class>

    </filter>

    <!-- 定义filter-mapping,让上面的Filter过滤所有的用户请求 -->

    <filter-mapping>

        <filter-name>requestContextFilter</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

    <!-- 定义Struts的核心Servlet -->

    <servlet>

        <servlet-name>action</servlet-name>

        <servlet-class>org.apache.struts.action.ActionServlet
        </servlet-class>

        <load-on-startup>2</load-on-startup>

    </servlet>

    <!-- 定义Struts的核心Servlet拦截所有*.do请求 -->

    <servlet-mapping>

        <servlet-name>action</servlet-name>

        <url-pattern>*.do</url-pattern>

    </servlet-mapping>

    <!-- 关于Struts标签库的配置 -->

    <jsp-config>

        <!-- 配置bean标签 -->

        <taglib>

            <taglib-uri>/tags/struts-bean</taglib-uri>

            <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>

        </taglib>

        <!-- 配置html标签 -->

        <taglib>

            <taglib-uri>/tags/struts-html</taglib-uri>

            <taglib-location>/WEB-INF/struts-html.tld</taglib-location>

        </taglib>

        <!-- 配置logic标签 -->

        <taglib>

            <taglib-uri>/tags/struts-logic</taglib-uri>

            <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>

        </taglib>

    </jsp-config>

</web-app>

posted on 2009-07-19 10:22 jadmin 阅读(65) 评论(0)  编辑  收藏

只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问