spring、mybatis整合原始碼簡單分析
配置
<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執行流程沒有分析,後續文章會做分析。由於作者水平有限,文章存在錯誤之處,肯請斧正,謝謝!