问题描述:
整合ssm(Spring+Spring MVC+Mybatis)框架时,模拟用户之间转账操作时使用了Spring的事务管理器,当程序发生异常时,事务并没有进行回滚,和没配事务管理器一样,但是通过Junit进行单元测试,跳过Controller层直接调用Service层进行转账操作时,事务管理器又可以正常起作用。所以大致可以确定问题出现在Spring和SpringMVC的整合上。
原因分析:
由于是JavaWeb项目,所以Spring的配置文件是通过ContextLoaderListener来加载的,也就是当Web容器一创建时便会加载Spring配置文件从而初始化IOC容器,此时由于Spring配置文件中配置了事务管理器,所以当扫描到@Service注解时实例化到IOC当中的Service是经过了事务增强的,然后将这个Service注入到Controller中。但是当SpringMVC的配置文件加载时,会再一次扫描@Service和@Controller,此时注入到Controller中的是没有经过事务增强的Service,在多上下文的情况下,如果同一个bean被定义了两次,后面一个优先,也就是后面的会覆盖前面的。这也就解释了问题描述当中出现的场景,当我们通过Tomcat启动整个项目时,这两个配置文件都会被加载,Controller中最终拿到的Service是没有经过事务增强的,当模拟异常时自然不会有事务回滚;而当我们通过Junit进行单元测试时,由于并没有使用tomcat,而是手动加载spring配置文件,此时Spring MVC的配置文件并没有起作用,所以我们在测试类中拿到的Service是经过了事务增强的,也就有了事务管理的功能,转账碰到异常自然会回滚。
解决方案:
经过大致的原因分析,解决问题的思路已经很明显了,只要能保证Spring的配置文件不扫描@Controller注解,Spring MVC的配置文件不扫描@Service注解,让两者各司其职,互不影响,就不会出现上面的问题了。可以对配置文件进行如下修改,达到注解过滤的效果。
ApplicationContext.xml:
<!--开启注解扫描--> <context:component-scan base-package="com.dzp"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/> </context:component-scan>
对于SpringMVC.xml有如下三种配置方案:
第一种:让springMVC只扫描Controller层的包。这也是最简单最直接的办法,因为springMVC是web框架,主要负责表现层,一般不会涉及到其他层。
<!--开启注解扫描-->
<context:component-scan base-package="com.dzp.controller"/>
第二种: 通过将<context:component-scan>的use-default-filters属性设置为false,来关闭它的默认扫描行为,这时通过配置<context:include-filter>来扫描@Controller(以及可能会用到的@RestController)就可以了,意思就是其他的都不扫描,只扫描@Controller(和@RestController)
<!--开启注解扫描--> <context:component-scan base-package="com.dzp" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/> </context:component-scan>
第三种:通过配置<context:exclude-filter>来不扫描Service,其他的都正常扫描。
<!--开启注解扫描--> <context:component-scan base-package="com.dzp"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
对于以上SpringMVC的三种配置方案,一般直接使用第一种,然后第三种虽然也能达到效果,但是是不推荐使用的,因为原因分析中涉及到的问题影响的不仅仅是事务管理,而是整个AOP(面向切面编程,Spring的两大核心之一),由于Spring内置的事务管理也是通过AOP思想实现的,所以才会间接被影响到。而在实际项目开发中,可能用到AOP的不仅仅只是Service层,所以如果使用的第三种配置方案的话,每次使用切面都需要考虑springMVC配置文件,无形之间增加了工作量。