问题描述

如果使用传统的方式来开发Spring项目,要部署在Tomcat上面,一般会依赖Spring与Spring MVC,在Tomcat的web.xml中会配置一个加载service的配置文件,这个在Tomcat启动的时候会进行加载,会生成一个Spring的容器。

默认情况下,Tomcat会在资源目录下加载配置servlet名称的另外一个xml配置文件,比如servlet名称为test,那么会加载test-servlet.xml配置文件,如果使用Spring MVC的话,会解析这个配置文件并且再生成一个Spring的容器,同时设置Tomcat启动时创建的那个容器为父容器。

 

容器分离的影响:

  • 一般在父容器中会加载service,dao之类的东西,不加载controller
  • 在mvc容器中只加载controller

两个容器负责不同的bean,但是如果在父容器中配置了一些AOP想要处理controller的内容,因为容器的隔离,AOP就不会生效。

解决方案

如果明白为什么会出现父子容器问题,那么可以想到如下方案

  • 如果只需要处理service的bean,那么只在父容器的配置文件中操作
  • 如果只处理controller的bean,那么在mvc的配置文件中修改
  • 如果同时要处理service,还要处理controller,那么在两个配置文件中都进行修改

 

一般我们在Spring的配置文件application.xml中对Service层代码配置事务管理,可以对Service的方法进行AOP增强或事务处理如事务回滚,但是遇到一个问题,在Controller类中调用Service层方法,配置的事务管理会失效,查询相关资料发现原因。其实Spring和SpringMVC俩个容器为父子关系,Spring为父容器,而SpringMVC为子容器。也就是说application.xml中应该负责扫描除@Controller的注解如@Service,而SpringMVC的配置文件应该只负责扫描@Controller,否则会产生重复扫描导致Spring容器中配置的事务失效。

因此正确的配置方式应该为:

首先我会在Spring的applicationContext.xml文件中配置配上下面这段代码:

<context:component-scan base-package="com.chuxin.platform,com.chuxin.core">
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

也就是扫描:component的时候把Controller排除掉

而在SpringMVC的dispatcher-servlet.xml配置文件中配置下面这段代码:

<context:component-scan base-package="com.chuxin.platform.controller" use-default-filters="false">
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

意思是,mvc只扫描controller。其他的component注解就不扫描

为什么这么配置呢,就是为了方式父子上下文扫描重叠的问题。Spring的applicationContext.xml是主上下文,而SpringMVC的dispatcher-servlet.xml是一个子上下文,如果父子上下文扫描重叠就会导致事务不生效。

最后
经过测试,其实问题主要在于SpringMVC的配置文件扫包范围,Spring的配置文件就算也扫了@Controller注解,但是在SpringMVC会重新扫描一次,事务管理的Service只要没被重新扫描就不会出现事务失效问题。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐