spring和mybatis集成過程當中,咱們能夠經過MapperFactoryBean的方式配置Mapper接口。可是這樣須要在配置文件中,爲每一個mapper配置相同的代碼塊,浪費時間。關鍵對於代碼潔癖的人來講,一點不能忍。html
<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的方式,讓代碼更加簡潔優雅。java
可是,當我在項目中,按照文文檔配置bean後,仍舊出現properties文件中jdbc的參數沒法正常解析。spring
<?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中解決方法。sql
參考依賴apache
<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>
在搜索引擎中,找到了一種解決方法。session
原理:使用sqlSessionFactoryBeanName注入,不會當即初始化sqlSessionFactory, 因此不會引起提早初始化問題。
同時還應注意在配置org.mybatis.spring.SqlSessionFactoryBean這個Bean時,id不能爲sqlSessionFactory,若是爲這樣的話會致使MapperScannerConigurer在bean定義加載時,加載PropertyPlaceholderConfigurer還沒來得及替換定義中的變量mybatis
以上方法,可以正常解決問題,可是爲何呢?app
打開MapperScannerConfigurer,會發現它實現了spring的四個接口:ide
問題的關鍵在於第一個接口,它是幹什麼的呢?主要是容許代碼像xml同樣,註冊自定義Bean到BeanFactory中。spring-boot
經過這個接口,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. */
在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); // …… }
這是由於在spring.xml文件中,beans節點,咱們配置了**default-autowire="byName"**屬性。 開啓後,使得BeanFacotry在實例化bean的時候,會自動根據bean名字和屬性名字進行自動匹配。 從而在實例化MapperScannerConfigurer的時候,自動匹配到sqlSessionFactory屬性,觸發sqlSessionFactory的實例化,引起dataSource進行實例化,而此時properties屬性並無進行解析。
<?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>