前言
在一次学习ssm框架整合的时候,spring和spring-mvc配置文件分开,配置component-scan时重复扫描包路径,导致事务失效的问题分析和解决方案。
一、问题描述
上图可以看到,applicationContext.xml是Spring的配置文件,这里面配置了component-scan:
<context:component-scan base-package="com.ssm.test" />
这里就会吧com.ssm.test包下所有的被spring注解的类扫描进Spring的IOC容器中,包括@Controller,这里是没有什么问题的,但是在spring-mvc.xml中也是配置的一样的扫描路径:
<context:component-scan base-package="com.ssm.test" />
这时候事务就会失效,这是为什么呢,因为这里spring和spring-mvc是一个父子容器,就有两个ioc容器,不过spring是父容器,mvc是子容器。
二、问题分析
这里通过分析源码和断点方式进行分析:
- 首先在web.xml中配置了ContextLoaderLister监听器,在Servlet启动时触发这个监听器,来初始化spring父容器:
- 可以看到初始化时已经把service、cntroller等Bean扫描进父容器了,并且service是一个代理对象,我们知道spring事务就是通过代理对象来实现的,所以父容器里面controller下注入的service是代理对象就有事务功能:
- 然后我们再来看下mvc子容器初始化,这里首先获取父容器对象,然后创建mvc子容器里面会设置wac.setParent(parent)。
- 再来看下mvc子容器里面的ioc单例池里面扫描的Bean对象信息,可以看到service对象不是一个代理对象,同时注入到controller里面的service也不是代理对象,所以这里肯定就没有事务的功能了:
三、解决方案
经过上面的分析,很明显的发现了spring父容器和mvc子容器里面的Bean重复扫描了,而且父容器里面的service对象是代理对象,是我们需要的。
而spring的父子容器只能子容器访问父容器,而父容器不能访问子容器,所以这里我们可以把扫描对象在父子容器之间进行分配,那就是父容器扫描除了controller包的其他位置的Bean,而mvc子容器只需要扫描controller包下的对象就行了。
当某个controller需要注入service时,子容器可以访问父容器的特性,所以这里就可以把父容器中这个service代理对象注入到controller中,这样就完美解决了重复扫描和事务不生效的问题了。
- 首先修改spring配置文件applicationContext.xml下扫描包配置,排除掉controller包:
<context:component-scan base-package="com.ssm.test" >
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 再修改spring-mvc.xml配置文件下扫描包配置,只扫描controller包:
<context:component-scan base-package="com.ssm.test.controller" />
- 这时再来看一下父容器下ioc单例池信息,这里就没有controller的Bean了:
- 再来看下mvc子容器的ioc单例池信息,可以看到没有service等其他Bean了,只有和mvc相关的一些Bean信息,而且controller下注入的service正是父容器里面的那个service代理对象: