首先是要看的是如何将Spring整合一般的的Web应用,也就是说即使不使用Struts也是可以使用Spring的,当然这种整合的方法也适合于Struts的应用中。首要是要注册一个Spring提供的一个监听器,注册好了之后就可以使用Web应用服务器来进行启动Spring的容器,也是ApplicationContext的实力,然后就可以使用这个对象来获取Spring容器来管理的Bean。注册监听器的代码是:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
为了可以让这个监听器来加载Spring的配置文件,还需要指定Spring配置文件的位置。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
当然这个就是默认的值,如果你的配置文件正好放在这个位置,也正好叫这个名字就可以不用配置这个初始化参数,我建议最好配置一下,这样有利于维护。通过这样配置就可以使用Web容器来启动Spring容器了,那么我们怎么来获取这个ApplicationContext对象呢。这个监听器就是来产生这个ApplictionContext对象的,产生了以后将这个对象放在ServleContext作用域中,只要的这个这个对象的存储时的键值就可以在Servlet来获取这个ApplicationContext对象了。通过查看Spring的源码可以查看到这个键值WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,有了这个就可以很容易的得到这个对象了。即:ApplicationContext applicationContext=(ApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
但是这样来获取未免有些太麻烦了,谁会记得这个键值啊。因此Spring提供了另外的实现方案。Spring中提供了一个这样的工具类WebApplicationContextUtils,可以使用他的getWebApplicationContext方法来获取,通过查看他的源码可以看到他的实现的方案还是和我们前面的方法一样,这样就是提供了一个简单的封装。例如:WebApplicationContextUtils.getWebApplicationContext(this.getServletContext())如果没有得到这个对象那么就会返回null,因此我们还可以使用另外一个方法,getRequiredWebApplicationContext,如果返回的值是null将会抛出一个这样的异常:throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");因此建议使用后面这个方法。
那么怎么整合Struts的web应用呢?Struts应用也是一个Web应用,因此上面的方法同样适用。Spring 还提供了更好的, 特定于 Struts 的解决方案,这些方案有很多,但是我总结了一下主要包括以下的两类:一类就是适用Struts应用服务器来启动Spring容器,启动了之后就是在是Action种来进行使用上面的方式来获取ApplicationContext对象,然后就可以调用Spring容器中的Bean了。这种方式和我们在一般的Web应用中的实现是非常类似的。这类实现方案的缺点是Spring的侵入性太大,Struts必须依赖于Spring的API,耦合性比较高。因此很少被使用。另外一类的解决方案是使用Spring的容器来进行管理Action对象,使用Spring容器来进行Action的依赖注入。这种方案的优点就是让Struts感觉不到Spring的存在,可以很好的实现解藕操作。那么我们来具体的看一下这两类解决方案。
首先是第一类解决方案,在 struts 配置文件中注册 Struts 插件来加载应用程序上下文, 它会自动引用 Servlet 监听器加载的应用程序上下文作为它的父上下文, 以便可以引用其中声明的 Bean。这时他的功能和那个监听器的功能是基本一样的,因此这时候就不必再配置这个监听器了。配置的插件的代码如下所示:
<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的配置文件的地址,默认情况下, 该插件会利用 web.xml 文件中注册的 ActionServlet 实例的名称加上 �Cserlvet.xml 后缀作为文件名. 如果想要另外加载一个 Bean 配置文件, 可以在 contextConfigLocation 属性中指定文件名. 但此时需通过 servlet 配置 Spring 容器随 Web 应用的启动而初始化. 而不适用 Listener 配置。同样再Action的代码中使用前面的那种方式来访问ApplicationContext对象就可以了,只是这时候获取的ServletContext的方式不一样了。仅仅配置这个插件也是不够的,还需要配置一个Servlet,ContextLoaderServlet让这个Servlet随着这个服务器的启动来启动,如下就可以实现了
<servlet>
<servlet-name>contextLoader</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>,这种方案就完成了。
还有一种实现就是Spring 提供了一个 ActionSupport 对象, 这是 Action 类的一个子类, 通过它的 getWebApplicationContext() 方法可以获取到 Spring 的应用程序上下文,其他的配置也是一样的,这种方案基本上就是第一种的已给简单的改良,还是依赖了Spring的API,这两中方案都是不可取的,虽然可以实现整合的效果,但是这样做是我们所不推荐的。当然这个时候如果我们使用我们在前面注册的那个监听器也是可以的,这时候就不需要使用在Struts中来配置插件和在web.xml文件中来配置Servlet,这两中配置都是可以实现WEB 应用服务器来启动Spring容器,接下来要做的就是在Action中如何来访问ApplicationContext对象,第一种就是使用的WebApplicationContextUtils.getWebApplicationContext方法来实现,第二种就是继承的ActionSupport类,不管怎么样使用都必须依赖于Spring的API,如果对于一个不懂Spring的程序员做Web层的开发就比较困难的,显然这不是我们所想要的。
下面就介绍第二种解决方案,使用Spring容器来管理Action和普通的Bean一样,一样使用Spring提供的依赖注入。在 Spring 的应用程序上下文中声明 Struts 的 Action 对象, 使用Spring 的依赖注入来注入 Spring 应用程序上下文的其他 Bean。
首先我们还是需要注册我们使用的监听器ContextLoaderListener以及指定Spring配置文件的初始化参数。然后在Struts-config.xml文件中注册一个<controller>元素,例如:
<controller>
<set-property property="processorClass"value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>
然后在配置Action节点的时候不需要指定type就可以,然后在Spring的配置文件中和配置普通的Bean一样来进行配置,然后注入所依赖的Bean就可以了,然后在Action的excute方法中就可以直接使用这个依赖的Bean了,在applicationContext.xml中声明 Struts Action要求该 Bean的 name必须和它在 struts-config.xml文件中的路径一致. 因为该 <bean> 元素的 id 属性不能包含 / 字符, 所以应该用 name 属性代替。这样就可以使用Spring配置的方式来实现是否需要单例处理,对于一些线程安全问题可以很好的解决了。这样在Struts里就不会依赖Spring的API了,Struts根本就感觉不到Spring的存在了,可见这得益于我们配置的这个<controller>元素了,这个元素究竟是干嘛的的。我查看了一下他的官方的文档。这是官方文档上的一段话:
The <controller>
element allows you to configure the ActionServlet. Many of the controller parameters were previously defined by servlet initialization parameters in your web.xml
file but have been moved to this section of struts-config.xml
in order to allow different modules in the same web application to be configured differently. For full details on available parameters see the struts-config DTDDoc or the list below.这段话的意思就是我们可以在struts-config.xml这个文件中通过使用<controller>
element来配置ActionServlet的属性,那么我们在这里配置的属性是processorClass,也就是要拦截默认的处理的请求使用Spring提供的这个DelegatingRequestProcessor来替换原来的RequestProcessor,这个类是RequestProcessor的一个子类,因此也可以实现请求的处理,在此的处理方式基本上还是调用父类的方法,只是在获取Action对象的时候使用的是Spring的容器来进行创建,这样就可以实现Spring对Action的管理了。然而这样做有一个弊病,就是假如在一个WEB应用中我向既让spring的容器来管理Action,但是如果某个特殊的Action我不想让Spring来管理,就没有办法了,因此不利于Struts的扩展,那么最好的解决方案是什么呢?就是在我们配置Action节点的时候,指定type属性的值为:org.springframework.web.struts.DelegatingActionProxy,这样就不需要在配置这个<controller>元素了,这样就可以将Struts的控制的粒度降低了,这才是最终的解决方案,也许大部分人会问,这样做多麻烦啊,每次都要配置type属性,那么长的一串谁记得住啊,还要频繁的进行复制粘贴吗?Struts的设计人员其实早就意识到了这一点了,因此在Action的节点处有一个extends的属性,指定为前面配置了type属性的节点就可以了。这样最终最优秀的解决方案就出现了。
在这里我把最终的解决方案放在最后还比较详细的分析了前面不好的解决方案,其实就是要搞清楚整合的原理,而不是简单的记住整合的步骤,这个步骤这么多,假如那一天我忘记了就麻烦了,如果我记住了原理我可以试探性的取整合,当然在以后整合Struts 2的时候,就可以轻而易举的上手了。