spring配置错误重复扫描导致事务失效的问题解决

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

 

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

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

 

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

 

application-context-common.xml:

省略

application-context-datasource.xml:

      

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

             

                    

                            classpath:constants.properties

                    

             

             

      

spring-servlet.xml:

      

      

      

      

      

      

      

      

      

      

      

      

        

      

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

             

                    

                            classpath:constants.properties

                            classpath:conf/installation.properties

                    

             

             

      

 

web.xml:

      

              contextConfigLocation

             

                     classpath:application-context-*.xml

                     classpath:springbeans/*-beans.xml

                     classpath:springmvc-servlet.xml

             

      

      

              springmvc

              org.springframework.web.servlet.DispatcherServlet

             

                     contextConfigLocation

                     classpath*:springmvc-servlet.xml

             

      

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中标红的配置:

              contextConfigLocation

             

                     classpath:application-context-*.xml

                     classpath:springbeans/*-beans.xml

                     classpath:springmvc-servlet.xml

             

      

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

而如下配置:

      

              springmvc

              org.springframework.web.servlet.DispatcherServlet

             

                     contextConfigLocation

                     classpath*:springmvc-servlet.xml

             

      

这个是用来处理所有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,而反过来不行。

 

于是,如果对于这两份配置没有很好的认识,不注重对象初始化的分类,尤其是使用这样的包扫描形式统一初始化,很容易造成满足条件的对象被初始化两次。不同的配置文件其作用是不一样的,不要将所有的初始化操作都放到一个配置文件中,更不要重复配置。重复加载配置文件,导致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)的扫包范围,达到以下效果:

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 配置中的classpath:conf/installation.properties移至application-context-datasource.xml

 

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

 

       以下是相关日志分析:

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

你可能感兴趣的:(Spring)