大家知道, 在spring mvc中, 在applicationContext.xml 和 dispatch-servlet.xml中都可以进行spring 的配置, 那么他们有什么区别呢:
我们先看一下Spring 官方文档:
Spring lets you define multiple contexts in a parent-child hierarchy. The applicationContext.xml defines the beans for the "root webapp context", i.e. the context associated with the webapp. The spring-servlet.xml (or whatever else you call it) defines the beans for one servlet's app context. There can be many of these in a webapp, one per Spring servlet (e.g. spring1-servlet.xml for servlet spring1, spring2-servlet.xml for servlet spring2). Beans in spring-servlet.xml can reference beans in applicationContext.xml, but not vice versa. All Spring MVC controllers must go in the spring-servlet.xml context. In most simple cases, the applicationContext.xml context is unnecessary. It is generally used to contain beans that are shared between all servlets in a webapp. If you only have one servlet, then there's not really much point, unless you have a specific use for it.
可见, applicationContext.xml 和 dispatch-servlet.xml形成了两个父子关系的上下文,经过测试 , 发现:
1) 一个bean如果在两个文件中都被定义了(比如两个文件中都定义了component scan扫描相同的package), spring会在application context和 servlet context中都生成一个实例,他们处于不同的上下文空间中,他们的行为方式是有可能不一样的(见下面描述的问题)。
2) 如果在application context和 servlet context中都存在同一个 @Service 的实例, controller(在servlet context中) 通过 @Resource引用时, 会优先选择servlet context中的实例。
PS:
ApplicationContext.xml 是spring 全局配置文件,用来控制spring 特性的
dispatcher-servlet.xml 是spring mvc里面的,控制器、拦截uri转发view
使用applicationContext.xml文件时是需要在web.xml中添加listener的:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
其实如果直接使用SpringMVC是可以不添加applicationContext.xml文件的。
使用applicationContext.xml文件时是需要在
web.xml
中添加listener的:
org.springframework.web.context.ContextLoaderListener
而这个一般是采用非spring mvc架构,如使用struts之类而又想引入spring才添加的,这个是用来加载Application Context。
如果直接采用SpringMVC,只需要把所有相关配置放到xxx-servlet.xml中就OK了。
在Spring 里配置Mybatis 事务时出现的问题:
按照网上的说明, 我在applicationContext.xml文件中增加了如下的配置:
1. applicationContext.xml
< bean id = "sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean" > < property name = "dataSource" ref = "dataSource" > </ property > < property name = "configLocation" value = "classpath:mybatis.xml" > </ property > < property name = "mapperLocations" value = "classpath:com/sjl/dao/*-mapper.xml" > </ property > </ bean > < bean id = "idao" class = "com.sjl.dao.IdaoImpl" > < property name = "sqlSessionFactory" ref = "sqlSessionFactory" > </ property > </ bean >
< bean id = "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" > < property name = "dataSource" ref = "dataSource" > </ property > </ bean > < tx:annotation-driven transaction-manager = "transactionManager" />
2. 然后在dao中使用 @Transactional 注解声明事务
package com.sjl.base; import java.io.Serializable; import java.util.HashMap; import java.util.List; import javax.annotation.Resource; import org.springframework.transaction.annotation.Transactional; import com.sjl.common.EntityClassUtil; import com.sjl.common.Pager; import com.sjl.dao.Idao; public class AbstractBaseDao<T, PK extends Serializable> implements BaseDao <T, PK> { @Resource private Idao<T, PK> idao; private Class entityClass = EntityClassUtil.getEntityClass(getClass()); <strong><span style="color:#ff0000;" > @Transactional </span></strong> public void save(T entity) { idao.save(entity); throw new RuntimeException(); } public void delete(PK pk) { idao.delete(entityClass, pk); } public Pager<T> findByPage( int pageOffset, int pageSize) { HashMap<String, Integer> map = new HashMap(); map.put("pageOffset" , pageOffset); map.put("pageSize" , pageSize); Pager<T> pager = idao.findByPage(entityClass, map); return pager; } }
其中的idao定义如下: <pre name="code" class = "java" > package com.sjl.dao; import java.io.Serializable; import java.util.List; import java.util.Map; import org.mybatis.spring.support.SqlSessionDaoSupport; import org.springframework.stereotype.Repository; import com.sjl.common.Pager; @Repository ( "idao" ) public class IdaoImpl<T, PK extends Serializable> extends SqlSessionDaoSupport implements Idao<T, PK> { public void save(T entity) { this .getSqlSession().insert(entity.getClass().getName()+ ".add" , entity); } public void delete(Class<T> entityClass, PK pk) { this .getSqlSession().insert(entityClass.getName()+ ".delete" , pk); } public Pager<T> findByPage(Class<T> entityClass, Map param){ Pager<T> pager = new Pager(); List<T> pageContent = this .getSqlSession().selectList(entityClass.getName()+ ".findPageContent" , param); pager.setPageContent(pageContent); int count = this .getSqlSession().selectOne(entityClass.getName()+ ".findTotal" ); pager.setTotalCount(count); return pager; } }
经过以上配置,按照道理应该没有问题了, 但是程序运行后发现事务始终没有启动,连transaction-manager都没有进入, 找了很久没有发现问题出在哪里。后来看到网上同样的配置能运行成功, 比较后发现他是在普通Java Application方式下运行成功的,我把自己的程序也在普通的Java Application方式下运行, 发现transaction起作用了,由此感觉应该是Spring MVC环境下出现的问题,而且应该是transaction 的配置没有起作用, 偶然记起在自己的配置中,dispatch-servlet.xml和applicationContext.xml中 配置的component-scan都扫描了相同的包,这样的话, 虽然在application Context中定义了transaction-manager, 但是这个transaction-manager的配置却是作用在application context中定义的bean上面的。根据前面讲到的application context和dispatcher-servlet context的关系, dispatcher-servlet context 和application context中都存在着相同类型的service bean 实例, 那么controller将优先调用dispatcher-servlet context 中的service bean,而dispatcher-servlet context 中由于没有配置transaction manager, 所以生成的service bean实例没有事务的植入(aop), 当然就无法启动事务了。
解决办法:
1. 在applicationContext.xml 中修改component-scan配置如下:
< context:component-scan base-package = "com.sjl" > < span style = "white-space:pre" > </ span > < context:exclude-filter type = "annotation" expression = "org.springframework.stereotype.Controller" /> //不扫描标记@Controller的类 </ context:component-scan >
2. 在dispatcher-servlet.xml中修改component-scan配置:
< context:component-scan base-package = "com.sjl.controller" > </ context:component-scan > .//只扫描controller package
经过上面的配置, Service bean实例就只会在application Context中生成,就可以使用transaction-manager管理的声明式事务了。
教训:
在application context和dispatcher-servlet定义的bean最好不要重复, dispatcher-servlet最好只是定义controller类型的bean。