SpringMVC對包的掃描範圍擴大後,致使的事務配置不生效問題

在整合新的ssh框架的時候,spring+springmvc+hibernate的時候發現一個問題,就是持久層在使用hibernateTemplate的時候,並不會自動實現事務的提交,SpringMVC對包的掃描範圍擴大後,致使的事務配置不生效問題前端

首先配置的是Spring容器的初始化加載的application文件,而後是SpringMVC的前端控制器(DispatchServlet),當配置完DispatchServlet後會在Spring容器中建立一個新的容器git

其實這是兩個容器,Spring做爲父容器,SpringMVC做爲子容器web

web.xml中對Spring的配置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>

applicationContext.xml使用AOP聲明式事務配置express

<!--  聲明式容器事務管理 ,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>

遇到的問題是:經過Hibernate執行save方法後,數據未能插入到DB中而且控制檯也沒有打印出SQL(控制檯沒有輸出)設計模式

經過仔細排查,閱讀網絡文章後,發現問題出如今spring-servlet.xml中:<context:component-scan base-package="com.zhimajp.auction" /> 網絡

上述配置的結果是:SpringMVC對Service和Dao的全部package進行了掃描裝載session

問題分析:

一、Spring與SpringMVC屬於父子容器關係。框架啓動時先啓動Spring容器,然後啓動SpringMVC容器。子容器能夠訪問父容器中的Bean,而父容器不能訪問子容器中的Beanmvc

二、因爲SpringMVC在掃描時擴大了掃描範圍,裝載了@Service標識的類的實例,從而致使Controller層在注入Service時,實際注入的是子容器中的Service實例app

三、事務被配置在父容器中,Spring父容器在裝載Service時會同時應用事務配置,而SpringMVC只是單純加載Service的實例

解決的辦法以下

applicationContext.xml掃包排除掉Controller

<context:component-scan base-package="com.bdqn.cc">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

springmvc.xml掃包只掃描controller

<context:component-scan base-package="com.bdqn.cc" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

springmvc配置文件 use-default-filters="false",由於use-default-filters的值默認是true,也就是掃描所有的帶有@Controller、@Service等註解的包了,加上以後則只掃描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加載 。

打印結果以下:

總結

一、當事務交由Spring管理時,Spring負責管理session中事務的開啓、關閉、flush等步驟,開發者只需調用例如save、update方法便可

二、當web項目框架中存在父子容器,且事務由父容器管理時,就應當注意SpringMVC對包的掃描範圍而且只需掃描Controller組件。官方推薦:父子容器應當各執其責

三、若是子容器加載了Service的話,則在該實例上事務並不會生效。也就是Spring不會在service的方法被調用時自動開啓事務

四、基於2中的前提:SpringMVC應只加載web相關配置(視圖配置、Controller註解掃描),由Spring加載數據源、事務配置、Service和Dao註解掃描

詳細的案例demo能夠查看個人碼雲https://gitee.com/chenduotang/projects new-ssh項目demo

相關文章
相關標籤/搜索