本文共 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!