1.1 Struts2插件:
从理论上来讲,Struts 2可以与任何框架整合,甚至是现在还没有问世的框架,这听起来不可思议,但却是真的。因为Struts 2提供了一种非常灵活的可扩展方式:插件,通过各种插件,Struts 2可以与任何Java EE框架进行整合。Struts 2已经提供了各种插件,用于与大部分流行的Java EE框架进行整合。
1.2 插件概述
Struts 2的插件完全超出了Struts 1插件的概念,Struts 1中的插件只是一种类似于Listener的机制。Struts 2插件则是一个JAR文件,这个JAR文件可以用于扩展、改变或者添加Struts 2的功能。安装Struts 2插件也非常简单,只需要将该插件的JAR文件复制到Web应用中即可。
注意:JSON插件的示例来看,使用Struts 2插件是非常简单的事情。实际上,Struts 2的插件非常类似于Eclipse的插件,完全是可插拔式的。当需要使用该插件时,将该插件的JAR文件复制到Web应用的WEB-INF/lib路径下即可;如果不再需要使用该插件,删除该JAR文件即可。
每个 Struts 2 的插件 JAR 文件都必须包含一个名为 struts-plugin.xml 的配置文件, struts-plugin.xml文件的内容与普通的struts.xml文件内容完全相同。
把这个包含struts-plugin.xml文件的JAR文件复制到WEB-INF/lib目录下之后,Struts 2会自动加载该 JAR 文件中的 struts-plugin.xml 文件。通过这种方式,我们就可以在struts-plugin.xml文件中定义自己需要扩展的部分,这些定义几乎可以覆盖到Struts 2的绝大部分,包括:
- 定义新包和新的Result类型,包括定义默认Action。
- 覆盖Struts 2框架的常量值。
- 定义自己新的拦截器。
- 改变默认的拦截器引用。
- 引入扩展点的实现类。
从上面的介绍中可以看出,Struts 2的插件几乎覆盖到Struts 2框架的方方面面,特别是重定义拦截器。因为拦截器是Struts 2框架的核心,开发者可以提供自己的拦截器,并且可以改变系统默认的拦截器引用。这意味着:如果开发者愿意,完全可以自己实现Struts 2的绝大部分底层功能。当然,一般没有这个必要,而且绝对不推荐这么做。
对于Struts 2所需要的大部分通用工作,例如与Spring、JSF、SiteMesh等框架的整合, Struts 2都提供了相应的插件。
值得指出的是,当容器中使用多个Struts 2插件时,每个插件加载的顺序是随机的,尽量不要让一个插件依赖于另一个插件(因为可能出现的情形是,被依赖的插件还没有加载,此时将导致依赖于该插件的插件无法正常运行)。
到目前为止,Struts 2 应用中可以包含三种类型的配置文件:struts-default.xml(包含在struts2-core.2.3.1.2.jar文件中)、struts-plugin.xml(包含在各插件JAR文件中)和struts.xml(应用相关的配置文件,当然也可以使用include将一个文件拆分成多个配置文件)。
(1)struts-default.xml(包含在struts2-core.2.3.1.2.jar文件中)。
(2)struts-plugin.xml文件(包含在各插件JAR文件中)。
(3)struts.xml(应用相关的配置文件)。
从上面的加载顺序可以看出,应用相关的配置文件总是最后才加载的。因为应用可以依赖于插件,每个插件都可依赖于Struts 2的核心,但插件之间尽量不要互相依赖。
提示:
从Struts 2.3.1.2开始,Struts 2允许在配置文件中为<struts .../>元素指定order属性,通过该属性可指定Struts 2配置文件的加载顺序。
从系统实现的角度来看,如果我们需要扩展Struts 2的功能,可以通过提供新的拦截器、新的Bean实例来实现;如果从系统结构的角度来看,如果我们需要扩展Struts 2的功能,则通过提供新的插件即可。
为了在Web应用中使用Spring框架,当然需要将Spring框架的JAR文件复制到Web应用中。正如前面所介绍的,通常建议复制dest路径下的spring.jar文件,这个文件是Spring框架最全的类库,使用该文件比使用分模块的类库更加简单。
除此之外,Spring 框架还依赖于 commons-logging.jar 文件,因此还需要将该文件复制到Web应用的WEB-INF/lib路径下。
为了完成Spring和Struts 2的整合,还必须安装Struts 2的Spring插件。正如前面所介绍的,安装Struts 2插件是非常简单的,只需要将struts2-spring-plugin-2.3.1.2.jar文件复制到Web应用的WEB-INF/lib路径下即可。
一旦在Web应用中安装了Spring插件,即可充分利用该插件提供的如下功能。
- 可以通过Spring来创建所有的Action。
- 也可以在Struts创建了某个对象(通常是Action实例)之后,Spring将其依赖的组件自动注入该对象。
- 提供拦截器来完成自动装配。
除此之外,在使用Spring容器之前,必须先完成Spring容器的初始化。为了完成Spring容器的初始化,Spring提供一个ContextLoaderListener类,该类可以作为Web应用的 Listener使用,它会在Web应用启动时自动查找WEB-INF/下的applicationContext.xml配置文件(Spring的配置文件),并且根据该文件来创建Spring容器。
因此,如果Web应用中只有一个Spring配置文件,文件名为“applicationContext.xml”,并将该文件放在Web应用的WEB-INF路径下,则只需在web.xml文件中增加如下一段即可。
<!-- 根据默认配置文件来初始化Spring容器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
如果有多个配置文件需要载入,则应该使用<context-param>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为“contextConfigLocation”的参数。因此,配置context-param时指定的参数名应该是“contextConfigLocation”。
如果有多个配置文件,则需要使用<context-param>元素来确定多个配置文件。事实上, ContextLoaderListener是通过调用ContextLoader来创建Spring容器的。在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参数,如果有该参数,则加载该参数指定的配置文件;加载该参数后,将该参数以指定的符号分解成字符串数组,每个字符串元素指定一个配置文件。
因此,如果应用中的Spring配置文件有多个,则应该采用如下形式的web.xml文件来创建Spring容器。
<?xml version="1.0" encoding="GBK"?>
<!-- 配置Web应用配置文件的根元素,并指定配置文件的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<!-- 指定多个Spring的配置文件 -->
<context-param>
<!-- 配置一个contextConfigLocation参数 -->
<param-name>contextConfigLocation</param-name>
<!-- 多个配置文件之间以,隔开 -->
<param-value>/WEB-INF/daoContext.xml ,/WEB-INF/applicationContext.xml</param-value> </context-param> <!-- 采用Listener完成Spring容器的初始化 --> <listener>
<listener-class>org.springframework.web.context.ContextLoader Listener </listener-class>
</listener>
</web-app>
如果没有 contextConfigLocation 指定配置文件,则 Spring 自动查找 WEB-INF 路径下的“applicationContext.xml”配置文件;如果有contextConfigLocation,则利用该参数指定的配置文件来创建Spring容器;如果无法找到合适的配置文件,Spring将无法正常初始化。
2.3 整合Spring的思考
对于一个基于B/S架构的Java EE应用而言,用户请求总是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。此时有一个问题:控制器应该如何获得业务逻辑组件?
最容易想到的策略是,直接通过 new 关键字创建业务逻辑组件,然后调用业务逻辑组件的方法,根据业务逻辑方法的返回值确定结果。
在实际的应用中,很少见到采用上面的访问策略,因为这是一种非常差的策略。不这样做至少有如下三个理由。
控制器直接创建业务逻辑组件,导致控制器和业务逻辑组件的耦合降低到代码层次,不利于高层次解耦。
每次创建新的业务逻辑组件时导致性能下降。
为了避免这种情况,实际开发中采用工厂模式或服务定位器模式来取得业务逻辑组件。对于采用服务定位器的模式,是远程访问的场景,在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无须理会该业务逻辑组件的创建,直接调用即可,但在调用之前,必须先找到该服务——这就是服务定位器的概念,传统以EJB为基础的Java EE应用通常采用这种模式。
对于轻量级的Java EE应用,工厂模式则是更实际的策略。因为在轻量级的Java EE应用里,业务逻辑组件不是EJB,通常就是一个POJO,业务逻辑组件的生成通常由工厂负责,而且工厂可以保证该组件的实例只需一个就够了,可以避免重复实例化造成的系统开销浪费。
采用工厂模式,可将控制器与业务逻辑组件的实现类分离开,从而提供更好的解耦。
在采用工厂模式的访问策略中,所有业务逻辑组件的创建由工厂负责,业务逻辑组件的运行也由工厂负责,而控制器只需定位工厂实例即可。
现在的问题是,控制器如何访问到 Spring 容器中的业务逻辑组件?为了让 Action 访问Spring的业务逻辑组件,有两种策略。
- 让Spring管理控制器,并利用依赖注入为控制器注入业务逻辑组件。
- 控制器利用自动装配获取所需的业务逻辑组件。