spring配置错误重复扫描导致事务失效的问题解决
发布日期:2021-07-30 03:26:41 浏览次数:2 分类:技术文章

本文共 5187 字,大约阅读时间需要 17 分钟。

事情的起因是,调试过程中,发现有一处事务不起效。后来发现是spring配置文件被改动。

 

于是对各个spring配置文件进行了分析。

涉及到四个文件。分别为application-context-common.xml、application-context-datasource.xml、spring-servlet.xml、web.xml。

 

在这里先给出四个配置文件的部分内容,着重关注标红的部分:

 

application-context-common.xml:

省略

application-context-datasource.xml:

       <bean id="propertyConfigurer"

              class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

              <property name="locations">

                     <list>

                            <value>classpath:constants.properties</value>

                     </list>

              </property>

              <property name="fileEncoding" value="utf-8" />

       </bean>

spring-servlet.xml:

       <!-- 注释扫描 -->

       <context:component-scan base-package="com.hikvision.cms.pms.**.action" />

       <context:component-scan base-package="com.hikvision.cms.pms.**.service" />

       <context:component-scan base-package="com.hikvision.cms.pms.**.dao" />

       <context:component-scan base-package="com.hikvision.cms.pms.**.api" />

       <context:component-scan base-package="com.hikvision.cms.pms.ref.**.impl"/>

       <context:component-scan base-package="com.hikvision.cms.pms.common.servlet"/>

       <context:component-scan base-package="com.hikvision.cms.pms.common.entity.web" />

       <context:component-scan base-package="com.hikvision.cms.pms.common.ldap" />

       <context:component-scan base-package="com.hikvision.cms.pms.ref" />

       <context:component-scan base-package="com.hikvision.cms.pms.api" />

       <aop:aspectj-autoproxy proxy-target-class="true" />

        

       <bean id="propertyConfigurer"

              class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

              <property name="locations">

                     <list>

                            <value>classpath:constants.properties</value>

                            <value>classpath:conf/installation.properties</value>

                     </list>

              </property>

              <property name="fileEncoding" value="utf-8" />

       </bean>

 

web.xml:

       <context-param>

              <param-name>contextConfigLocation</param-name>

              <param-value>

                     classpath:application-context-*.xml

                     classpath:springbeans/*-beans.xml

                     classpath:springmvc-servlet.xml

              </param-value>

       </context-param>

       <servlet>

              <servlet-name>springmvc</servlet-name>

              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

              <init-param>

                     <param-name>contextConfigLocation</param-name>

                     <param-value>classpath*:springmvc-servlet.xml</param-value>

              </init-param>

       </servlet>

application-context-common.xml中定义了一些bean,自动扫描ldap包下的bean

application-context-datasource.xml中定义数据库相关的bean(1.定义数据源,注入所需数据源;2.定义SqlSessionFactoryBean),定义事务管理的transactionManager的bean,设置为CGLIB代理,开启切面,使用切面管理事务,在service层的增删改查等方法上建立连接点。配置PropertyPlaceholderConfigurer

spring-servlet.xml中自动扫描action、service、dao、api、impl、servlet、web、ldap、ref等包下的bean,设置为CGLIB代理。配置拦截器、配置数据转换功能,配置一些bean。配置PropertyPlaceholderConfigurer

web.xml中配置过滤器、servlet、listener等

 

让我们来看下web.xml中标红的配置:

<context-param>

              <param-name>contextConfigLocation</param-name>

              <param-value>

                     classpath:application-context-*.xml

                     classpath:springbeans/*-beans.xml

                     classpath:springmvc-servlet.xml

              </param-value>

       </context-param>

该节点指派的application-context-*.xml、springbeans/*-beans.xml、springmvc-servlet.xml是用于实例化除servlet之外的所有对象的,项目中绝大多数的service和dao层操作都由ContextLoaderListener传递给Spring来进行实例化, 主要用于整个Web应用程序需要共享的一些组件

而如下配置:

       <servlet>

              <servlet-name>springmvc</servlet-name>

              <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

              <init-param>

                     <param-name>contextConfigLocation</param-name>

                     <param-value>classpath*:springmvc-servlet.xml</param-value>

              </init-param>

       </servlet>

这个是用来处理所有servlet的,没有它就无法通过请求地址来调用相应的Controller。如果不指定,则会按照注释中所描述地那样自动加载"工程名-servlet.xml"配置文件。在这里我们通过init-param指定为spring-servlet.xml文件。

 

Sping+SpringMVC的框架中,IoC容器的加载过程:

基本上Web容器(Tomcat)先加载ContextLoaderListener(加载application-context-*.xml、springbeans/*-beans.xml、springmvc-servlet.xml),然后生成一个IoC容器。

然后再实例化DispatchServlet时候会加载对应的配置文件(spring-servlet.xml),再次生成Controller相关的IoC容器。

对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext,而反过来不行。

 

于是,如果对于这两份配置没有很好的认识,不注重对象初始化的分类,尤其是使用<context:component-scan base-package="controller" />这样的包扫描形式统一初始化,很容易造成满足条件的对象被初始化两次。不同的配置文件其作用是不一样的,不要将所有的初始化操作都放到一个配置文件中,更不要重复配置。重复加载配置文件,导致bean被上述两个IoC容器重复加载,生成两份实例,不仅会浪费资源,还会导致莫名其妙的故障。如果存在定时任务,则定时任务也会被执行两次。

 

根本原因: 由此可见spring-servlet.xml文件被加载了两次,Spring 容器和Spring MVC容器分别都初始化了spring-servlet.xml中定义的实例。

 

解决方案:

1.去掉context-param中配置的springmvc-servlet.xml,使springmvc-servlet.xml只被扫描一遍。

2.通过修改两个配置文件(application-context-common.xml、spring-servlet.xml)的<context:component-scan base-package=""/>扫包范围,达到以下效果:

Spring MVC的配置文件spring-servlet.xml严格限制只初始化Controller层实例;

Spring的配置文件application-context-common.xml严格限制只初始化除Controller层的其他层实例;

 

       修改配置后,报错IllegalArgumentException: Could not resolve placeholder……

原因是占位符进行了多次配置,application-context-datasource.xml、spring-servlet.xml 中均配置了PropertyPlaceholderConfigurer,而且略有不同,我们保留application-context-datasource.xml中的那份,且将spring-servlet.xml 配置中的<value>classpath:conf/installation.properties</value>移至application-context-datasource.xml

 

回到一开始导致问题暴露的那个修改,当时是把spring-servlet.xml中的<aop:aspectj-autoproxy proxy-target-class="true" />删除了,这个配置是设置为CGLIB代理。那么生成的两份实例中,其中DispatcherServlet创建的一部分实例没有用CGLIB代理,覆盖了ContextLoaderListener所创建的使用了CGLIB代理的实例,导致事务无法生效。

 

       以下是相关日志分析:

将spring包日志的debug打开,启动项目至正常运行后,在日志中搜索“Creating CGLIB proxy”,原本CGLIB代理两次时搜索结果为738,错误修改删除CGLIB代理配置后,搜索结果为369,为原来的一半,符合之前的分析。正确修改各个配置文件后,再次搜索结果为284(又去掉了spring-servlet.xml文件中的CGLIB代理配置,所以代理个数减少)。

转载地址:https://blog.csdn.net/u010898743/article/details/100067857 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:修改查询结果导致脏读问题的保护性拷贝方案 和关闭Mybatis缓存方案对比
下一篇:java统计数量List<Map<String,Object>

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月07日 22时58分39秒