对于spring与struts2的集成有多种方案,在网上找到下面说明还算清楚,所以这里将它直接引用过来,后面将对其原理详加分析
一、需要的JAR文件为:Spring和Struts2框架本身需要的JAR文件以及他们所依赖的JAR文件,比如commons-logging.jar等等,另外还需要Struts2发布包中的struts2-spring-plugin-x.xx.jar。
二、在web.xml中增加WebApplicationContext的相应配置,以下两种配置方式本质是一样的。
1. Servlet 2.3及以上版本可以使用监听器,相应配置如下:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
如果spring配置文件被命名为applicationContext.xml,并且放在WEB-INF目录下,则不需要配置<context-param>,因为ContextLoaderListener默认在WEB-INF目录下寻找名为applicationContext.xml的文件。若存在多个Spring配置文件,则在<param-value>中依次列出,之间以逗号隔开。
2. Servlet 2.3以下版本由于不支持<listener>,需要配置<servlet>,格式如下:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>contextLoaderServlet</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
如果spring配置文件被命名为applicationContext.xml,并且放在WEB-INF目录下,则不需要配置<context-param>,因为ContextLoaderListener默认在WEB-INF目录下寻找名为applicationContext.xml的文件,或者是名字为contextConfigLocation的ServletContext参数所指定的文件。由于该Servlet配置只是为了在容器启动时能启动ContextLoaderServlet使其工作,而不需要引用该Servlet,所以不需要配置<servlet-mapping>。
三、在web.xml中完成加载WebApplicationContext之后,接下来就可以做到Spring和Struts2的整合了。整合有两种方法,分别叙述如下:
1. 第一种实现方法:
1) 将Struts的业务逻辑控制器类配置在Spring的配置文件中,业务逻辑控制器中引用的业务类一并注入。注意,必须将业务逻辑控制器类配置为scope=”prototype”!
示例如下:
<bean id=”LoginAction” class=”yaso.struts.action.LoginAction”>
<property name=”loginDao” ref=”LoginDao”/>
</bean>
2) 在struts.xml或者等效的Struts2配置文件中配置Action时,指定<action>的class属性为Spring配置文件中相应bean的id或者name值。示例如下:
<action name=”LoginAction” class=”LoginAction”>
<result name=”success”>/index.jsp</result>
</action>
2. 第二种实现方法:
1) 业务类在Spring配置文件中配置,业务逻辑控制器类不需要配置,Struts2的Action像没有整合Spring之前一样配置,<action>的class属性指定业务逻辑控制器类的全限定名。
2) 业务逻辑控制器类中引用的业务类不需要自己去初始化,Struts2的Spring插件会使用bean的自动装配将业务类注入进来,其实业务逻辑控制器也不是Struts2创建的,而是Struts2的Spring插件创建的。默认情况下,插件使用by name的方式装配,可以通过增加Struts2常量来修改匹配方式:设置方式为:struts.objectFactory.spring.autoWire = typeName,可选的装配参数如下:
a) name:等价于Spring配置中的autowire=”byName”,这是缺省值。
b) type:等价于Spring配置中的autowire=”byType”。
c) auto:等价于Spring配置中的autowire=”autodetect”。
d) constructor:等价于Spring配置中的autowire=” constructor”。
四、如果原先在Struts2中使用了多个object factory,则需要通过Struts2常量显式指定object factory,方式如下:struts.objectFactory = spring;如果没有使用多个object factory,这一步可以省略。
五、可以通过设增加Struts2常量来指定是否使用Spring自身的类缓存机制。可以设定的值为true或false,默认为true。设置方式为:struts.objectFactory.spring.useClassCache = false。
六、至此,完成了两种方式的整合。比较这两种整合方式,其本质是一样的。不同之处在于,使用第二种自动装配的方式时,由于没有在Spring中配置业务逻辑控制器,所以需要对其配置一些AOP之类的内容时就很难实现了。
上面的两种集成方案我认为是当下最好的两种集成方案,至于为什么可以这样集成需要对spring以及struts2(确切的说是xwork)都要有所有了解才能很好的理解其中的含义,尤其是xwork的内部结构及运行源程序要有所了解.xwork内部组件是由自己的容器来管理的,并且这些组件大部分都是可以替换的,其中有一个组件负责其它组件的创建,其中最主要的xwork三个最重要的组件:Action,Interceptor,Result还有相应的快捷方法,这个类就是ObjectFactory它是一个独立的类,没有实现任何的接口,主要业务集中在两个方法,在下面的用红色标注的两个方法
/** * Build a generic Java object of the given type. * * @param clazz the type of Object to build * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} */ public Object buildBean(Class clazz, Map extraContext) throws Exception { return clazz.newInstance(); } /** * @param obj */ protected Object injectInternalBeans(Object obj) { if (obj != null && container != null) { container.inject(obj); } return obj; } /** * Build a generic Java object of the given type. * * @param className the type of Object to build * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} */ public Object buildBean(String className, Map extraContext) throws Exception { return buildBean(className, extraContext, true); } /** * Build a generic Java object of the given type. * * @param className the type of Object to build * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext} */ public Object buildBean(String className, Map extraContext, boolean injectInternal) throws Exception { Class clazz = getClassInstance(className); Object obj = buildBean(clazz, extraContext); if (injectInternal) { injectInternalBeans(obj); } return obj; }这两个方法之间有依赖关系,即字集串构建方法(后一个方法)需要调用前一个方法(类对象构建),上面就是xwork原始ObjectFactory对象构建对象的核心方法,另外需要注意一点是上面还有一个方法injectInternalBeans它用来注入xwork内部管理组件的,与现在讨论问题不是很紧密,但在整个流程中也是很重要环节,如果需要了解详情请参考其它资料.
在xwork中为了和spring集成专门对ObjectFactory进行了继承扩充,这个类被称为:SpringObjectFactory这个类对上面两个方法进行覆盖,赋予了新含义,具体的代码如下:
/** * Looks up beans using Spring's application context before falling back to the method defined in the {@link * ObjectFactory}. * * @param beanName The name of the bean to look up in the application context * @param extraContext * @return A bean from Spring or the result of calling the overridden * method. * @throws Exception */ public Object buildBean(String beanName, Map extraContext, boolean injectInternal) throws Exception { Object o = null; try { o = appContext.getBean(beanName); } catch (NoSuchBeanDefinitionException e) { Class beanClazz = getClassInstance(beanName); o = buildBean(beanClazz, extraContext); } if (injectInternal) { injectInternalBeans(o); } return o; } /** * @param clazz * @param extraContext * @throws Exception */ public Object buildBean(Class clazz, Map extraContext) throws Exception { Object bean; try { bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); } catch (UnsatisfiedDependencyException e) { // Fall back bean = super.buildBean(clazz, extraContext); } bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName()); // We don't need to call the init-method since one won't be registered. bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName()); return autoWireBean(bean, autoWiringFactory); }
在创建对象的时候第一个方法会先在spring容器的上下文中进行查询对象(这里实际实际对应上面的集成方案一);如果在上下文中没有查找action定义,退而求其次调用第二个方法使用autoWiringFactory(这是一个与spring上下文相关的对象)创建action实例对象并注入容器中的其它依赖对象(这里实际对应上面的集成方案二).在struts2中的有一个与spring的集成插件名称为:StrutsSpringObjectFactory它实际只是一个适配器,让它更适用于struts2自身它实际完全使用SpringObjectFactory功能
通过上面的分析基本可以很清楚的看到在struts的action配置中的有一个class属性,例如:<action name="login" class="com.risen.core.action.ProductAction" method="listProduct">在没有与spring进行集成的时候,此处只能填写对应action类的全名称,从ObjectFactory中的buildBean方法可以看它只支持类的名称,当它与spring进行集成之后,这个属性的值便有了更多的选择,理论上它可以填写任何标识,因为SpringObjectFactory采用的策略是先到spring容器中使用这个名称在容器中检索一次,如果可以检索成功,就会使用检索到的这个对象(当然它已经完成依赖注入),就会直接使用这个对象(前面的方式一),如果没有检索到这个对象会将这个名称作为action类的全名称,进行实例化,并使用使用spring容器中的对象对这个实例化对象进行注入操作,再将这个注入后的对象返回(方式二)从代码上看这第二种方式的代价似乎大一些,但是它不需要管理action
在实际中action究竟需不需要管理,我觉得没有一个定论,但是作为开发者你要清楚集成机制,比如开发者可能会使用spring的扫描组件对需要管理的组件进行扫描,在配置文件使用<context:component-scan base-package="com.xxx.core.action" />,这时你就需要十分的清醒,对action扫描之后它们在spring容器中的检索名称是什么,即name或id值,如果在进行扫描的时候没有在@Repository、@Service、@Controller等标注中指定名称,它们的默认名称是类名称的首字母小写形式,此时你就需要在对应的action类型属性中填写这个名称,而不是action的全类名,如果需要一个简单的原则,可以归纳成:如果action配置使用全路径名称,就不要对action对象进行管理,如果不使用全路径类名称,则需要对action进行管理,但千万要把两都的标识名称匹配上,否则会出错.
关于这上面两种方案优缺点上面方案已经提到,第一个方案配置文件写得多一些,但是可控性会更强一些,第二种方案配置会更少,因为action不需要在spring配置文件中出现,这种自动化更,但程序员介入手段相对更少,两种方案各有千秋.
另外上面只着眼于aciton对象的生成与创建,实际通过上面的分析你会发现xwork中其它组件的创建也会从中得利,即像Interceptor,Result等对象也可以很容易的获取spring容器中的对象,当然至于需不需要那是另一回事
=============================================================================================
下面是关于webwork与spring集成一些方法,可以在集成的时候进行参考
参考:
http://wiki.opensymphony.com/display/WW/WebWork+2+Spring+Integration
http://forum.iteye.com/viewtopic.php?t=8509
http://forum.iteye.com/viewtopic.php?t=9939
下载:
http://xwork-optional.dev.java.net/
昨天使用webwork和spring,使用的是SpringObjectFactory方法,突然发现validator没发生作用,折腾半天,
换成external-ref的方法,可以了.但是觉得external-ref的方法太麻烦了.
就是去http://xwork-optional.dev.java.net/下载了一下最新的源码,换回SpringObjectFactory的方式,发现问题解决了(重载getClassInstance解决了这个问题).
于是仔细研究了一下几种组合的方法,根据前面参考中的文章,整理了一下.
看了xwork-optional的源码,发现其实三种方法的源码都包含在这里了,当然也包括external-ref这种方法的源码,只是换了包名和文件名,但是源码基本没变.你可以仔细看看.
1.External-Ref
这种方法看起来比较烦琐,可能现在都改用第3种方法了.
第一步:在web.xml里面增加一个listener,如下
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <listener>
- <listener-class>com.opensymphony.xwork.spring.SpringExternalReferenceResolverSetupListener</listener-class>
- </listener>
第二步:在Spring里面配置类似Dao之类的bean,例如
- <beanid="myDAO"class="com.ryandaigle.persistence.MyDAO"singleton="true"/>
第三步:配置XWork.xml,例如
- <packagename="default"extends="webwork-default"
- externalReferenceResolver="com.opensymphony.xwork.spring.SpringExternalReferenceResolver">
- <interceptors>
- <interceptorname="reference-resolver"class="com.opensymphony.xwork.interceptor.ExternalReferencesInterceptor"/>
- <interceptor-stackname="myDefaultWebStack">
- <interceptor-refname="defaultStack"/>
- <interceptor-refname="reference-resolver"/>
- </interceptor-stack>
- </interceptors>
- <default-interceptor-refname="myDefaultWebStack"/>
- <actionname="myAction"class="com.ryandaigle.web.actions.MyAction">
- <external-refname="DAO">myDAO</external-ref>
- <resultname="success"type="dispatcher">
- <paramname="location">/success.jsp</param>
- </result>
- </action>
- </package>
2.SpringObjectFactory
我一直用这种方法,因为以前觉得是xwork本身提供的方法,升级有保障.
配置方法:
第一步.在spring的 applicationContext.xml (根据实际情况决定) 里面定义你的action,例如
- <beanname="some-action"class="fully.qualified.class.name"singleton="false">
- <propertyname="someProperty"><refbean="someOtherBean"/></property>
- </bean>
可以看到,可以使用Spring的特性来给你的action设置属性等,当然也可以使用Spring的拦截器等 (可以使用不一定等于推荐使用)
注意一定是singleton="false",因为xwork的action是这样要求的.
第二步.在xwork.xml里定义你的action定义
- <actionname="myAction"class="some-action">
- <resultname="success">view.jsp</result>
- </action>
第三步.要使上面的关联生效,还要用我们的SpringObjectFactory来替换Xwork的默认ObjectFactory.
最新的SpringObjectFactory里面,有两种方法,其中我觉得A方法更直观一些.
A:修改web.xml
- <!--这个是spring的listener,可以改为你的自定义的spring的Listenter-->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <!--这个必须在SpringContextLoaderListener的后面-->
- <listener>
- <listener-class>com.opensymphony.xwork.spring.SpringObjectFactoryListener</listener-class>
- </listener>
B.在spring的applicationContext.xml (根据实际情况决定)里面定义一个bean,例如
- <beanid="spring-object-factory"class="com.opensymphony.xwork.spring.SpringObjectFactory"
- init-method="initObjectFactory"/>
这样Spring会自动调用initObjectFactory方法来替换Xwork的默认ObjectFactory
3.ActionAutowiringInterceptor
这个方法是最近出现的,可能是最简洁的方法,但是不知道性能上有没有问题,我觉得jdk1.4以后应该没有任何问题吧,至于实际效果你的自己测试一下.
第一步:配置web.xml
- <!--这个是spring的listener,可以改为你的自定义的spring的Listenter-->
- lt;listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- lt;/listener>
第二步配置xwork.xml里面的拦截器
- <interceptors>
- <interceptorname="autowire"class="com.opensymphony.xwork.spring.interceptor.ActionAutowiringInterceptor">
- <paramname="autowireStrategy">@org.springframework.beans.factory.config.AutowireCapableBeanFactory@AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE</param>
- </interceptor>
- <interceptor-stackname="autowireDefault">
- <interceptor-ref="autowire"/>
- <interceptor-ref="defaultStack"/>
- </interceptor-stack>
- </interceptors>
你的拦截器里都要包含autowire,例如这个autowireDefault就相当于以前的默认的拦截器了.
其中的拦截器策略可以配置,上面配置的是根据类型,如果不配置,默认是根据名字.
一共有四种策略:
AUTOWIRE_CONSTRUCTOR
AUTOWIRE_BY_TYPE
AUTOWIRE_BY_NAME
AUTOWIRE_AUTODETECT
这种方法执行原理就是查找你的action的所有字段,如果和Spring的定义bean有相同的,就自动设置.
假设你的Spring的applicationContext.xml里面有这样一个定义:
- <beanid="userManager"class="com.test.UserManager"/>
如果你在xwork.xml 里面定义的某个action有一个字段叫userManager,那么在运行时刻就会自动被设置为Spring的配置文件里定义的Bean.
以上如有不对指出,还请指出
=============================================================================================
最近突然想到一个问题
以前在用struts2(注解)+spring
struts的action拖给spring管了(spring的bean在Ioc容器范围内默认都是singlen的),但是没有加@scope("prototype")却从来没有出现过线程安全问题。
而一年前还在学校时做ssh2(没用注解)练习时,不在bean后加prototype都会出现线程安全问题,当时所有的action后都加了scope=prototype。
难道注解和不用注解,struts2创建action的方式不一样?
于是做了个测试
- publicclassTestActionextendsBaseAction{
- @Autowired
- privateUserServiceuserService;
- @Action("test")
- publicStringtest()throwsException{
- System.out.println("actionHashCode:"+this.hashCode());
- userService.getUserName();
- return"success";
- }
- }
- @Service
- publicclassUserService{
- publicvoidgetUserName(){
- System.out.println("serviceHashCode:"+hashCode());}
- }
连续运行3次发现输出:
- actionHashCode:9928297
- serviceHashCode:32262619
- actionHashCode:13620718
- serviceHashCode:32262619
- actionHashCode:19792917
- serviceHashCode:32262619
输出结果说明 每次的action是不一样的
而每次的service是同一个也就是单例的
(难道action并没有交个spring托管)
又把以前在学校做的老项目(纯xml)拿来 把以前的scope="prototype"去掉
发现action确实是单例的 再加上scope="prototype"后action不是单例了
所以struts2(注解) 在和spring集成时action默认是new的,不用注解spring扫描action的话也是用的new。
而非注解时如果把action加入bean的话默认是单例的。
所以如果大家struts2是用注解的话就不需要在action上加@scope("prototype")了;