• SpringMVC对包的扫描范围扩大后,导致的事务配置不生效问题


    问题场景

    项目使用的框架:Spring 4.1.4 + Hibernate 4.3.8 + MySQL。

    web.xml中对Spring的配置:

    <!-- 把 Spring 容器集成到 Web 应用里面 -->
      <listener>
        <listener-class>
          org.springframework.web.context.ContextLoaderListener
        </listener-class>
      </listener>
    
      <!-- spring配置文件 -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/applicationContext.xml</param-value>
      </context-param>
    
      <!--DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,
      而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。-->
      <!--DispatcherServlet会默认加载[servlet-name]-servlet.xml文件-->
      <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>

    项目使用AOP声明式事务配置

    <!--  声明式容器事务管理 ,transaction-manager指定事务管理器为transactionManager -->
        <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="query*" read-only="true"/>
                <tx:method name="get*" read-only="true"/>
                <tx:method name="save*" rollback-for="Exception" propagation="REQUIRED"/>
                <tx:method name="update*" rollback-for="Exception" propagation="REQUIRED"/>
            </tx:attributes>
        </tx:advice>
        <aop:config>
            <!--只对业务逻辑层实施事务 -->
            <aop:pointcut expression="execution(* com.zhimajp.auction.service.impl..*.*(..))" id="busiLogicService"/>
            <aop:advisor advice-ref="transactionAdvice" pointcut-ref="busiLogicService"/>
        </aop:config>

    采用annotation方式声明Spring Bean。

    遇到的问题是:通过Hibernate执行save方法后,数据未能插入到DB中并且控制台也没有打印出SQL(控制台没有输出)。

    解决问题

    通过仔细排查,阅读网络文章后,发现问题出现在spring-servlet.xml中:

     <!--扫描注解包 配置这条便可移除 <context:annotation-config/> --> <context:component-scan base-package="com.zhimajp.auction" /> 

    上述配置的结果是:SpringMVC对Service和Dao的所有package进行了扫描装载。

    问题分析:

    1、Spring与SpringMVC属于父子容器关系。框架启动时先启动Spring容器,而后启动SpringMVC容器。子容器可以访问父容器中的Bean,而父容器不能访问子容器中的Bean。

    2、由于SpringMVC在扫描时扩大了扫描范围,装载了@Service标识的类的实例,从而导致Controller层在注入Service时,实际注入的时子容器中的Service实例。

    3、事务被配置在父容器中,Spring父容器在装载Service时会同时应用事务配置,而SpringMVC只是单纯加载Service的实例。

    问题解决:

    将SpringMVC的包扫描限定在Controller。

      <context:component-scan base-package="com.zhimajp.auction.controller" /> 

    进一步

    打印容器管理的bean名称

    我们使SpringMVC扫描Controller和Service,Spring扫描Service和DAO。

    使用以下代码打印父子窗口管理的bean名称:

         WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
            String[] definationBeanNames = webApplicationContext.getBeanNamesForAnnotation(Service.class);
            List<String> names = new ArrayList<String>(Arrays.asList(definationBeanNames));
            Collections.addAll(names, webApplicationContext.getBeanNamesForAnnotation(Controller.class));
            Collections.addAll(names, webApplicationContext.getBeanNamesForAnnotation(Repository.class));
            System.out.println("Spring 父容器管理的Bean:");
            for(String beanName : names){
                System.out.println(beanName);
            }
    webApplicationContext
    = RequestContextUtils.getWebApplicationContext(request); definationBeanNames = webApplicationContext.getBeanNamesForAnnotation(Service.class); names = new ArrayList<String>(Arrays.asList(definationBeanNames)); Collections.addAll(names, webApplicationContext.getBeanNamesForAnnotation(Controller.class)); Collections.addAll(names, webApplicationContext.getBeanNamesForAnnotation(Repository.class)); System.out.println("SpringMVC 子容器管理的Bean:"); for(String beanName : names){ System.out.println(beanName); }

     我们发现父子容器同时维护了Service层的类的实例,并且应该是两个独立的实例。

    只使用子容器,而完全不使用父容器

    现在我们测试另外一个场景

    将web.xml中,注释掉ContextLoaderListener,修改配置为:

    <!--DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,
      而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。-->
      <!--DispatcherServlet会默认加载[servlet-name]-servlet.xml文件-->
      <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:/applicationContext.xml;/WEB-INF/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>

    移除了父容器,所有配置文件全部交由SpringMVC加载 。

    打印结果如下:

    子容器管理所有的bean,而父容器为null(代码抛出了空指针)。

    项目同样运行正常。

    总结

    1、当事务交由Spring管理时,Spring负责管理session中事务的开启、关闭、flush等步骤,开发者只需调用例如save、update方法即可。

    2、当web项目框架中存在父子容器,且事务由父容器管理时,就应当注意SpringMVC对包的扫描范围并且只需扫描Controller组件。官方推荐:父子容器应当各执其责。

    3、如果子容器加载了Service的话,则在该实例上事务并不会生效。也就是Spring不会在service的方法被调用时自动开启事务。

    4、基于2中的前提:SpringMVC应只加载web相关配置(视图配置、Controller注解扫描),由Spring加载数据源、事务配置、Service和Dao注解扫描。

  • 相关阅读:
    web服务器12 中间件函数
    web11 路由
    web服务器15 jsonp格式接口
    web服务器14 cors跨域资源共享 报错及解决方案
    小程序弹窗(模态框)遮罩层 弹窗右上角按钮关闭
    数据库1 mysq数据库的安装全套
    MySQL数据库3 的 where语句 and 和 or运算符
    MySQL数据库2 增删改查
    MySQL分组排序取每组第一条
    IDEA频繁闪退解决
  • 原文地址:https://www.cnblogs.com/zhaojz/p/8176052.html
Copyright © 2020-2023  润新知