配置
<bean id="localDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://192.168.31.14:3366/lios?characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> ... </bean> <!-- 創建SqlSessionFactory,同時指定數據源--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="localDataSource"/> <property name="configLocation" value="classpath:sqlmap-config.xml"/> <property name="mapperLocations"> <list> <value>classpath*:com/lios/mybatis/mapper/*.xml</value> </list> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.lios.mybatis.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean>
MapperScannerConfigurer
這個bean有什麼作用呢,MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,該接口可以讓我們實現自定義並註冊bean,具體可以參考關於BeanDefinitionRegistryPostProcessor接口使用的文章,無疑分析入口還是從 org.springframework.context.support.AbstractApplicationContext#refresh
方法開始.
分析
掃描basePackages,封裝MapperFactoryBean,註冊到spring容器
在 AbstractApplicationContext
類的 refresh
方法裏,會調用:
invokeBeanFactoryPostProcessors(beanFactory);
調用BeanFactory的後置處理器,向容器中註冊自定義Bean,一直跟到 PostProcessorRegistrationDelegate
類的 invokeBeanFactoryPostProcessors
方法中這段代碼:
// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. boolean reiterate = true; while (reiterate) { reiterate = false; postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (!processedBeans.contains(ppName)) { //getBean方法會初始化MapperScannerConfigurer BeanDefinitionRegistryPostProcessor pp = beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class); registryPostProcessors.add(pp); processedBeans.add(ppName); // 調用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法 pp.postProcessBeanDefinitionRegistry(registry); reiterate = true; } } }
跟到MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法中,關鍵代碼:
... scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
這段代碼用於掃描MapperScannerConfigurer中配置的basePackage路徑下的文件.繼續根進 ClassPathBeanDefinitionScanner
類的 scan
方法:
// Register annotation config processors, if necessary. if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
doScan
方法會調用 ClassPathMapperScanner#doScan
類中的doScan方法:
// 調用父類doScan方法,掃描basePackage下的mapper的接口文件,封裝成Set<BeanDefinitionHolder> doScan(basePackages); Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions;
processBeanDefinitions
方法很重要:
GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 // 上面的註釋其實說的很清楚了,mapper接口實際的實體爲MapperFactoryBean definition.setBeanClass(this.mapperFactoryBean.getClass()); // 設置MapperFactoryBean屬性addToConfig元素 definition.getPropertyValues().add("addToConfig", this.addToConfig); ... // 設置MapperFactoryBean屬性sqlSessionTemplate元素 definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate); }
經過上面的流程,basePackages下的mapper接口已經註冊到容器中.
實例化MapperFactoryBean中SqlSessionFactory,解析xml配置文件
繼續回到 AbstractApplicationContext
類中的 refresh
中,會在該方法中初始化所有單例且是懶加載的bean,如果在應用中注入使用mapper接口時:
@Autowired UserInfoDao userInfoDao;
就會初始化該mapper實例,其實就是初始化 MapperFactoryBean
,spring會檢查該bean的屬性是否爲對象,依次初始化,由於 MapperFactoryBean中的屬性SqlSessionTemplate、addToConfig,由於SqlSessionTemplate已經在配置文件配置,繼而又會去初始化SqlSessionTemplate的屬性 org.mybatis.spring.SqlSessionFactoryBean
,因爲SqlSessionFactoryBean實現了 InitializingBean
接口,所以在初始化時會調用其 afterPropertiesSet
方法:
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); }
buildSqlSessionFactory
方法非常關鍵,用來解析mppaer xml文件,關鍵代碼:
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse();
這裏不作具體分析。
生成mapper接口動態代理類
當MapperFactoryBean中的屬性初始化完後,則繼續執行MapperFactoryBean的初始化流程,在 AbstractBeanFactory
類的 doGetBean
方法中:
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
調用了 AbstractBeanFactory
類的getObjectForBeanInstance方法:
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
因爲MapperFactoryBean實現了FactoryBean接口,所以纔可以向下執行代碼, 繼續調用了 FactoryBeanRegistrySupport
類的getObjectFromFactoryBean方法:
Object object = doGetObjectFromFactoryBean(factory, beanName);
繼續調用 FactoryBeanRegistrySupport
類中的doGetObjectFromFactoryBean方法:
... object = factory.getObject(); ...
原來調用了FactoryBean的getObject方法,這時則斷點執行到了 MapperFactoryBean
的getObject方法中:
return getSqlSession().getMapper(this.mapperInterface);
繼續執行到 org.apache.ibatis.session.Configuration
的getMapper方法:
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); }
上面代碼是不是很熟悉,原來爲mapper 接口創建了代理類 MapperProxy<T>
,當調用mapper接口中具體的方法操作數據庫時,其實執行的的是 MapperProxy<T>
中的invoke方法:
try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);
上面還有一個關鍵點就是,xml中解析的配置如何與spring容器中mapper bean相關聯呢,其實通過 DaoSupport
類中的 checkDaoConfig
方法,在 DaoSupport
類的 afterPropertiesSet
方法中調用,具體看MapperFactoryBean中的checkDaoConfig實現:
@Override protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); // mybatis中配置類 Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 添加mapper關聯 configuration.addMapper(this.mapperInterface); } catch (Exception e) { logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } }
到此爲止,已經分析完了mybatis與spring結合的源碼簡單說明,省略了大量的細節,以及mapper xml文件解析、sql執行流程沒有分析,後續文章會做分析。由於作者水平有限,文章存在錯誤之處,肯請斧正,謝謝!