事情的起因是,调试过程中,发现有一处事务不起效。后来发现是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">
spring-servlet.xml:
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
web.xml:
classpath:application-context-*.xml
classpath:springbeans/*-beans.xml
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中标红的配置:
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应用程序需要共享的一些组件。
而如下配置:
这个是用来处理所有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,而反过来不行。
于是,如果对于这两份配置没有很好的认识,不注重对象初始化的分类,尤其是使用
根本原因: 由此可见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 配置中的
回到一开始导致问题暴露的那个修改,当时是把spring-servlet.xml中的
以下是相关日志分析:
将spring包日志的debug打开,启动项目至正常运行后,在日志中搜索“Creating CGLIB proxy”,原本CGLIB代理两次时搜索结果为738,错误修改删除CGLIB代理配置后,搜索结果为369,为原来的一半,符合之前的分析。正确修改各个配置文件后,再次搜索结果为284(又去掉了spring-servlet.xml文件中的CGLIB代理配置,所以代理个数减少)。