在整合Spring和Struts 1.x时候,网上面的教程都是教大家在struts配置里面增加一个叫org.springframework.web.struts.ContextLoaderPlugIn的插件,增加以下的配置:
struts-config.xml:
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation" value="/WEB-INF/config.xml" />
</plug-in>
config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<bean id="helloWorldService" class="com.strutstest.service.impl.HelloWorldServiceImpl">
</bean>
<!--注意此处的映射必须与Struts中的动作对应 -->
<bean name="/helloWorld" class="com.strutstest.action.HelloWorldAction">
<property name="helloWorldService">
<ref bean="helloWorldService" />
</property>
</bean>
</beans>
一般都是这样的方式的;这里ContextLoaderPlugin 的作用是通过contextConfigLocation配置的位置加载Spring的配置文件,生成WebApplicationContext对象,并将该对象放入ServletContext中,
对应的Key为:
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "配置文件前缀"
//或者单纯的
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX
因此在struts运行时候就已经生成了对应的Spring环境了。Action可以被注册成Spring Bean,纳入Spring管理。
但是有些时候,可以直接使用DelegatingRequestProcessor和DelegatingActionProxy而不需要使用ContextLoaderPlugIn来实现Action的Bean化。其实是因为我们已经使用了ContextLoaderListener/ContextLoaderServlet初始化了Spring的环境,使用时候我们只需要将Action的Bean加入到对应的Spring配置文件即可。
对于
ContextLoaderPlugIn和
ContextLoaderListener/ContextLoaderServlet的区别主要在于两种方式生成的WebApplicationContext,以不同的Key存放在ServletContext中,ContextLoaderPlugIn是以
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "配置文件前缀"
//或者单纯的
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX
而ContextLoaderListener/ContextLoaderServlet则是
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
既然两个Key不同,那么
DelegatingRequestProcessor和
DelegatingActionProxy能识别到吗!
DelegatingRequestProcessor是Spring提供的一个继承了struts的RequestProcessor类,使用时候直接在struts-config加入下面代码即可:
<!--告诉Struts用DelegatingRequestProcessor来代替原来的RequestProcessor -->
<controller processorClass="org.springframework.web.struts.DelegatingRequestProcessor" />
由于DelegatingRequestProcessor继承于RequestProcessor,RequestProcessor是struts的核心控制器,通过其来控制Action的创建以及调用,Spring就是在这里做了手脚,Spring重写了RequestProcessor的processActionCreate方法,修改了创建Action方式,通过action 配置中path作为Bean Name,通过Spring容器获取正真的Action对象,然后执行对应的响应方法。
DelegatingRequestProcessor:
private WebApplicationContext webApplicationContext;
public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException{
super.init(actionServlet, moduleConfig);
if (actionServlet != null) {
this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig);
}
}
protected WebApplicationContext initWebApplicationContext(ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException {
return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig);
}
protected final WebApplicationContext getWebApplicationContext() {
return this.webApplicationContext;
}
protected Action processActionCreate(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws IOException {
Action action = getDelegateAction(mapping);
if (action != null) {
return action;
}
return super.processActionCreate(request, response, mapping);
}
protected Action getDelegateAction(ActionMapping mapping) throws BeansException {
String beanName = determineActionBeanName(mapping);
if (!getWebApplicationContext().containsBean(beanName)) {
return null;
}
return (Action) getWebApplicationContext().getBean(beanName, Action.class);
}
protected String determineActionBeanName(ActionMapping mapping) {
return DelegatingActionUtils.determineActionBeanName(mapping);
}
DelegatingActionUtils:
public static WebApplicationContext findRequiredWebApplicationContext(ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException {
WebApplicationContext wac = getWebApplicationContext(actionServlet, moduleConfig);
// If no Struts-specific context found, fall back to root context.
if (wac == null) {
wac = WebApplicationContextUtils.getRequiredWebApplicationContext(actionServlet.getServletContext());
}
return wac;
}
public static String determineActionBeanName(ActionMapping mapping) {
String prefix = mapping.getModuleConfig().getPrefix();
String path = mapping.getPath();
String beanName = prefix + path;
if (logger.isDebugEnabled()) {
logger.debug("DelegatingActionProxy with mapping path '" + path + "' and module prefix '" + prefix + "' delegating to Spring bean with name [" + beanName + "]");
}
return beanName;
}
public static WebApplicationContext getWebApplicationContext(ActionServlet actionServlet, ModuleConfig moduleConfig) {
WebApplicationContext wac = null;
String modulePrefix = null;
// Try module-specific attribute.
if (moduleConfig != null) {
modulePrefix = moduleConfig.getPrefix();
wac = (WebApplicationContext) actionServlet.getServletContext().getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + modulePrefix);
}
// If not found, try attribute for default module.
if (wac == null && !"".equals(modulePrefix)) {
wac = (WebApplicationContext) actionServlet.getServletContext().getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX);
}
return wac;
}
WebApplicationContextUtils:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
Assert.notNull(sc, "ServletContext must not be null");
Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Root context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) throws IllegalStateException {
WebApplicationContext wac = getWebApplicationContext(sc);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
return wac;
}
很明显在
DelegatingRequestProcessor的
init时候
WebApplicationContext就已经通过
DelegatingActionUtils.findRequiredWebApplicationContext方法获取到,而且很明显
findRequiredWebApplicationContext是先根据
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "配置文件前缀"
获取,如果没有就根据
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX
获取,如果再没有获取到,就根据
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
获取,如果还是没有获取到就直接抛出异常。
DelegatingRequestProcessor是会在
ServletContext查找
ContextLoaderPlugIn和
ContextLoaderListener/ContextLoaderServlet的Key,获取
WebApplicationContext,然后通过
WebApplicationContext获取对应的Action。因此
DelegatingRequestProcessor是可以在没有
ContextLoaderPlugIn的情况下正常运行的。
对于DelegatingActionProxy也是差不多的原理。
DelegatingActionProxy:
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
Action delegateAction = getDelegateAction(mapping);
return delegateAction.execute(mapping, form, request, response);
}
protected Action getDelegateAction(ActionMapping mapping) throws BeansException {
WebApplicationContext wac = getWebApplicationContext(getServlet(), mapping.getModuleConfig());
String beanName = determineActionBeanName(mapping);
return (Action) wac.getBean(beanName, Action.class);
}
protected WebApplicationContext getWebApplicationContext(ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException {
return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig);
}
protected String determineActionBeanName(ActionMapping mapping) {
return DelegatingActionUtils.determineActionBeanName(mapping);
}
DelegatingActionProxy在
execute时候也是通过
DelegatingActionUtils.findRequiredWebApplicationContext获取
WebApplicationContext,然后获取Action,来达到目的的。
附加:
配置使用Spring的
OpenSessionInView Filter时候,因为
OpenSessionInViewFilter是按照
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
这个key去拿spring配置的!
整理一下思路:
ContextLoaderPlugIn保存spring配置的key:
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "配置文件前缀"
//或者单纯的
ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX
ContextLoaderListener保存spring配置的key
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
而
OpenSessionInView是按照
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
这个名字去取得spring容器的!而你的应用程序却是按照
ContextLoaderPlugIn的key去取得spring容器的。所以,
OpenSessionInView模式失效!
如果使用了
ContextLoaderPlugIn的Spring容器,而
OpenSessionInViewFilter却获取了
ContextLoaderListener的Spring容器。这样会导致Hibernate的session异常关闭的问题。如果配置了两个Spring容器,则刪除
ContextLoaderPlugIn的加载方式。若只配置了
ContextLoaderPlugIn的加载方式,则改成
ContextLoaderListener加载方式。