Spring 整合 Struts2 之 深度解析

一、启动Spring

  对于使用Spring的Web应用,无须手动创建Spring容器,而是通过配置文件声明式的创建Spring容器。在Web应用中创建Spring容器有如下两种方式:

  • 直接在web.xml文件中配置创建Spring容器
    • 利用ServletContextListener实现
    • 采用load-on-startup Servlet实现
  • 利用第三方MVC框架的扩展点,创建容器

1.1 在web.xml中利用ServletContextListener监听器

  这种方式最常见,为了让Spring容器随Web应用的启动而自动启动,借助于ServletContextListener监听器即可完成,该监听器可以在Web应用启动时回调自定义方法(该方法就可以启动Spring容器)。

  Spring提供了一个ContextLoaderListener,该监听器类实现了ServletContextListener接口。该类可以作为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。如下:

<!-- 指定多个配置文件 -->
<context-param>
	<!-- 参数名为contextConfigLocation -->
	<param-name>contextConfigLocation</param-name>	
	<!-- 多个配置文件之间以","隔开 -->
	<param-value>WEB-INF/SpringConfig/*.xml,/WEB-INF/A.xml,/WEB-INF/applicationContext.xml</param-value>
</context-param>

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

注意:如果没有用contextConfigLocation制定配置文件,则Spring会自动查找WEB-INF/路径下的applicationContext.xml配置文件;如果有contextConfigLocation,则使用该参数确定的配置文件。如果无法找到合适的配置文件,Spring将无法正常初始化。
  Spring根据指定的配置文件创建WebApplicationContext对象,并将其保存在Web应用的ServletContext中。在大部分情况下,应用中的Bean无须感受到ApplicationContext的存在,只要利用ApplicationContext的IoC即可。如果需要在应用中获取ApplicationContext实例,则可以通过如下代码获取:

// 获取当前Web应用启动的Spring容器
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(ServletContex);

  当然也可以通过ServletContext的getAttribute方法获取ApplicationContext,但是用WebApplicationContextUtils类更方便。

1.2 采用load-on-startup Servlet机制

  1. load-on-startup元素标记容器是否在启动的时候就加载这个servlet(实例化并调用其init()方法)
  2. 它的值必须是一个整数,表示servlet应该被载入的顺序。当值为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet
  3. 当值小于0或者没有指定时,则表示容器在该servlet被选择时才会去加载
  4. 正数的值越小,该servlet的优先级越高,应用启动时就越先加载
  5. 当值相同时,容器就会自己选择顺序来加载
  6. <load-on-startup>x</load-on-startup>中x的取值1,2,3,4,5代表的是优先级,而非启动延迟时间。
<servlet> 
    <servlet-name>context</servlet-name> 
    <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet> 

注意:这种方式,spring3.0以后不再支持,建议使用监听器方式。你可以查看一下spring3.0的change log ,里面注明: removed ContextLoaderServlet and Log4jConfigServlet

1.3 利用第三方MVC框架的扩展点

  Struts有一个扩展点PlugIn,spring正是利用了PlugIn这个扩展点,从而提供了与Struts的整合。spring提供了PlugIn的实现类org.springframework.web.struts.ContextLoadPlugIn,这个实现类可作为struts的PlugIn配置,Struts框架启动时,将自动创建Spring容器为了利用struts的PlugIn创建Spring容器,只需要在struts配置文件struts-config.xml中增加如下片段即可:

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">  
    <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml" />  
</plug-in> 

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

二、MVC框架与Spring整合

  对于一个基于B/S构架的Java EE应用而言,用户请求总是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。
  控制器应该如何获得业务逻辑组件?在实际开发中,很少采用new关键字来创建业务逻辑组件,然后调用业务逻辑组件的方法,原因如下:

  • 控制器直接创建业务逻辑组件,导致控制器和业务逻辑组件的耦合降低到代码层次,不利于高层次解耦
  • 控制器不应该负责业务逻辑组件的创建,控制器只是业务逻辑组件的使用者,无须关心业务逻辑组件的实现
  • 每次创建新的业务逻辑组件将导致性能下降

  实际开发中,通常采用工厂模式,或者服务定位器模式。对于采用服务定位器模式,是远程访问的场景,在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无需理会该业务逻辑组件的创建,直接调用该服务即可,但在调用之前,必须先找到该服务——这就是服务定位器的概念。经典的JavaEE应用就是这种结构的应用。
  对于轻量级的JavaEE应用,工厂模式则是更实际的策略。因为在轻量级的JavaEE中,业务逻辑组件不是EJB,通常就是一个POJO,业务逻辑组件的生成通常应由工厂负责,而且工厂可以保证该组件的实例只需要一个就够了,可以避免重复实例化造成的系统开销。
  采用工厂模式,将控制器与业务逻辑组件的实现分离,从而提供更好的解耦。在采用工厂模式的访问策略中,所有的业务逻辑组件的创建由工厂负责,业务逻辑组件的运行也由工厂负责,而控制器只需要定位工厂实例即可,而采用Spring框架,则Spring称为最大的工厂。
为了让Action访问到Spring的业务逻辑组件,有两种策略:

  • Spring容器负责管理控制器Action,并利用依赖注入为控制器注入业务逻辑组件
  • 利用Spring的自动装配,Action将会自动从Spring容器中获取所需的业务逻辑组件

2.1 让Spring管理控制器

  Struts2的核心控制器首先拦截到用户请求,然后将请求转发给对应的Action处理,在此过程中,Struts2将负责创建Action实例,并调用其execute()方法。这个过程是固定的。现在的情形是:把Action实例交由Spring容器来管理,而不是由Struts2产生的,name核心控制器如何知道调用Spring容器中的Action,而不是自行创建Action实例呢?这个工作有Struts2提供的Spring插件(struts2-spring-plugin-*.jar)来完成。
  让Spring容器来管理应用中的控制器,可以充分利用Spring的IoC特性,但需要将配置Struts2的控制器部署在Spring容器中,因此导致配置文件冗余。Struts2提供的Spring插件提供了一种伪Action,在struts.xml文件中配置Action时,指定class属性时,不再指定Action的实际实际类,而是指定为Spring容器中的Bean ID,这样Struts2不再自己负责创建Action实例,而是直接通过Spring容器去获取Action对象。
代码示例详见:codes\08\8.7\spring-manage-action
注意:当Spring管理Struts2的Action时,一定要配置scope属性,因为Action里包含了请求的状态信息,必须为每个请求对应一个Action,所以不能将该Action实例配置成singleton行为,指定scope属性为prototype或request。
部署项目后,再启动过程中会有如下显示:

四月 05, 2017 2:50:18 下午 org.apache.struts2.spring.StrutsSpringObjectFactory info
信息: Initializing Struts-Spring integration...
四月 05, 2017 2:50:18 下午 com.opensymphony.xwork2.spring.SpringObjectFactory info
信息: Setting autowire strategy to name
四月 05, 2017 2:50:18 下午 org.apache.struts2.spring.StrutsSpringObjectFactory info
信息: ... initialized Struts-Spring integration successfully

  很简单,Struts-Spring集成初始化——>指定自动装配策略为byname——>Struts-Spring集成初始化成功

2.2 使用自动装配

  在自动装配下,Action还是由Spring插件创建,Spring插件在创建Action实例时,利用Spring的自动装配策略,将对应的业务逻辑组件注入Action实例中。这种整合配置策略的配置文件简单,但控制器和业务逻辑组件耦合又提升到了代码层次,耦合较高。如果不指定自动装配,则系统默认使用byName自动装配,如上。
  所谓自动装配,即让Spring自动管理Bean与Bean之间的依赖关系,无须使用ref显式指定依赖Bean。Spring容器会自动检查XML配置文件的内容,为主调Bean注入依赖Bean。自动装配可以减少配置文件的工作量,但会降低依赖关系的透明性和清晰性。
  通过使用自动装配,可以让Spring插件自动将业务逻辑组件注入Struts2的Action实例中。通过设置struts.objectFactFactory.spring.autoWrite常量可以改变Spring插件的自动装配策略,该常量可以接受如下几个值:

  • name:使用byName自动装配(默认值)
  • type:使用byType自动装配
  • auto:Spring插件会自动检测需要时使用哪种自动装配方式
  • constructor:与type类似,区别是constructor使用构造器来构造注入所需的参数,而不是使用设值注入方式

代码示例详见:codes\08\8.7\autowire
  示例代码采用的是byName自动装配策略(默认值),而Struts2的配置文件与原来相同,Action的class属性指定实现类来创建Action实例,而使用Spring进行依赖注入,注入时采用的byName方式,因此只需要将Bean的ID与注入元素的名称一样,就可以进行自动注入了。
注意:整合Spring框架与不整合时当然存在区别,只是这个区别不在这个配置文件中体现,而是在创建该Action实例时体现出来的。如果不整合Spring框架,则Struts2框架负责创建Action实例,创建成功后就结束了;如果整合Spring框架,则当Action实例创建完成后,Spring插件还会负责将Action所需的业务逻辑组件注入给该Action实例。
这种方式的缺点:

  • Action与业务逻辑组件的耦合低到代码层次,必须在配置文件中配置与Action所需控制器同名的业务逻辑组件。这不利于高层次解耦。
  • Action接收Spring容器的自动装配,代码的可读性较差

参考资料:

赞赏

阅读更多
换一批

没有更多推荐了,返回首页