背景
spring和mybatis集成过程中,我们可以通过MapperFactoryBean的方式配置Mapper接口。但是这样需要在配置文件中,为每个mapper配置相同的代码块,浪费时间。关键对于代码洁癖的人来说,一点不能忍。
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
使用MapperScannerConfigurer进行自动转换Mapper的方式,让代码更加简洁优雅。
但是,当我在项目中,按照文文档配置bean后,仍旧出现properties文件中jdbc的参数无法正常解析。
- 相关配置文件信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
default-autowire="byName">
<context:property-placeholder location="conf/*.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" lazy-init="true">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations" value="mapper/*.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.yanmushi.**.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
PS:请原谅在spring-boot,注解方式盛行的年代,还纠结xml中解决方法。
依赖版本
参考依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.1-RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
分析过程
在搜索引擎中,找到了一种解决方法。
原理:使用sqlSessionFactoryBeanName注入,不会立即初始化sqlSessionFactory, 所以不会引发提前初始化问题。
同时还应注意在配置org.mybatis.spring.SqlSessionFactoryBean这个Bean时,id不能为sqlSessionFactory,如果为这样的话会导致MapperScannerConigurer在bean定义加载时,加载PropertyPlaceholderConfigurer还没来得及替换定义中的变量
以上方法,能够正常解决问题,但是为什么呢?
为什么sqlsessionfactorybeanname注入呢?
打开MapperScannerConfigurer,会发现它实现了spring的四个接口:
- BeanDefinitionRegistryPostProcessor
- InitializingBean
- ApplicationContextAware
- BeanNameAware
问题的关键在于第一个接口,它是干什么的呢?主要是允许代码像xml一样,注册自定义Bean到BeanFactory中。
通过这个接口,MapperScannerConfigurer在spring初始化过程中,将扫描basePackage中的自定义mapper接口转换为MapperFactoryBean,并注册到BeanFactory中。
/**
* Extension to the standard {@link BeanFactoryPostProcessor} SPI, allowing for
* the registration of further bean definitions <i>before</i> regular
* BeanFactoryPostProcessor detection kicks in. In particular,
* BeanDefinitionRegistryPostProcessor may register further bean definitions
* which in turn define BeanFactoryPostProcessor instances.
*/
spring容器本身已经很好到解决了Bean管理问题。为什么会导致提前初始化呢?
在AbstractApplicationContext#refresh()方法中,详细注明类初始化的流程。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
MapperScannerConfigurer和PropertySourcesPlaceholderConfigurer两个类,都是BeanFactoryPostProcessor的实现类。但是MapperScannerConfigurer实现了BeanFactoryPostProcessor的子接口,BeanDefinitionRegistryPostProcessor。
在执行invokeBeanFactorPostProcessors(beanFacotry)方法时,是调用到PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List<BeanFactoryPostProcessor>)方法,在这个方法中,BeanDefinitionRegistryPostProcessor被优先执行。 所以,在执行MapperScannerConfigurer中不能关联需要读取properties文件的Bean,因为Properties没有被初始化。
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<String>();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<BeanFactoryPostProcessor>();
List<BeanDefinitionRegistryPostProcessor> registryPostProcessors =
new LinkedList<BeanDefinitionRegistryPostProcessor>();
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryPostProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryPostProcessor.postProcessBeanDefinitionRegistry(registry);
registryPostProcessors.add(registryPostProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
// Separate between BeanDefinitionRegistryPostProcessors that implement
// PriorityOrdered, Ordered, and the rest.
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
List<BeanDefinitionRegistryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanDefinitionRegistryPostProcessor>();
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
OrderComparator.sort(priorityOrderedPostProcessors);
registryPostProcessors.addAll(priorityOrderedPostProcessors);
// 此时去执行:postProcessor.postProcessBeanFactory(beanFactory)方法
invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry);
// ……
}
为什么我们使用sqlSessionFacotryBeanName,但是依旧会出现properties没有被引用的问题呢?
这是因为在spring.xml文件中,beans节点,我们配置了**default-autowire="byName"**属性。 开启后,使得BeanFacotry在实例化bean的时候,会自动根据bean名字和属性名字进行自动匹配。 从而在实例化MapperScannerConfigurer的时候,自动匹配到sqlSessionFactory属性,触发sqlSessionFactory的实例化,引发dataSource进行实例化,而此时properties属性并没有进行解析。
总结
- 关闭default-autowire属性;否则,需要更改SqlSessionFactory这个bean的id,避免自动关联。
- 如果只有一个数据源,MapperScannerConfigurer会自动关联的;如果有多个,可以通过sqlSessionFactoryBeanName来配置bean的名字。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="conf/*.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" lazy-init="true">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations" value="mapper/*.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.yanmushi.**.mapper"/>
<!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
</bean>
</beans>
参考文章
- http://www.mybatis.org/spring/zh/
- https://www.cnblogs.com/daxin/p/3545040.html
- http://blog.sina.com.cn/s/blog_8e5354210101i03l.html
- https://www.cnblogs.com/yxh1008/p/6012230.html