MyBatis原始碼閱讀——Spring載入MyBatis過程解析
我們平時在專案中都是用Spring來管理的,那麼,Spring是如何管理MyBatis的呢?我們來一探究竟。
程式設計式載入MyBatis
要了解Spring是如何載入MyBatis的,我想還是先來回顧一下我們是如何用程式設計的方式去載入MyBatis框架的
String resource = "mybatis/conf/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //從 XML 中構建 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //獲取session SqlSession session = sqlSessionFactory.openSession(); try { //獲取Mapper BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(1L); System.out.println(blog); // blog = mapper.selectBlog(1L); // System.out.println(blog); } finally { session.close(); }
在程式碼中,我們主要是構建SqlSessionFactory,而構建 SqlSessionFactory,需要配置檔案。那麼Spring中我們是怎麼使用的呢?
Spring配置Mybatis
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--注入資料庫連線池 --> <property name="dataSource" ref="dataSource"/> <!--配置mybatis全域性配置檔案:mybatis-config.xml --> <property name="configLocation" value="classpath:mybatis/conf/mybatis-config.xml"/> <!--掃描entity包,使用別名,多個用;隔開 --> <property name="typeAliasesPackage" value="entity"/> <!--掃描sql配置檔案:mapper需要的xml檔案 --> <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/> <!--配置外掛 --> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <value> dialect=mysql </value> </property> </bean> </array> </property> </bean> <!-- 配置掃描Dao介面包,動態實現Dao介面,並注入到spring容器中 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="top.yuyufeng.learn.mybatis.mapper"/> </bean>
從配置中,我們可以看出,這裡做了兩步操作:
- 構建SqlSessionFactory
- 把Mappers注入到Spring容器管理
Spring 載入SqlSessionFactory的過程:
首先我們看下SqlSessionFactory類的結構
SqlSessionFactory實現了InitializingBean介面,也就是說,在該Bean初始化的時候,我執行InitializingBean中的afterPropertiesSet()方法。那麼,我們直接進入SqlSessionFactory的afterPropertiesSet()方法:
public void afterPropertiesSet() throws Exception { //... //構建SqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); }
在該方法中,直接進行構建SqlSessionFactory,那麼來看下構建SqlSessionFactory的過程方法(刪去了部分):
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
configuration.setObjectFactory(this.objectFactory);
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
}
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
}
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
}
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
}
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
}
xmlConfigBuilder.parse();
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
for (Resource mapperLocation : this.mapperLocations) {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
//在上面的方法中主要進行的操作是:整理載入配置資訊Configuration,使用這些配置通過SqlSessionFactoryBuilder建立SqlSessionFactory,在最後呼叫了sqlSessionFactoryBuilder.build,我們看下這個是什麼:
深入程式碼,你就能看到這是過載的build方法,之前程式設計式載入MyBatis的時候呼叫的是上面的方法。
SqlSessionFactory已經建立完成了,那麼,接下來就應該把Mapper裝入Spring容器了。
Mappers注入到Spring容器管理的過程
我們是如何從Spring中拿Mapper物件的,這主要是MapperFactoryBean類的作用:
這是Mapper的獲取過程:
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
接下來就是Mappers注入到Spring容器管理的過程了:
它實現了BeanDefinitionRegistryPostProcessor介面,MapperScannerConfigurer->postProcessBeanDefinitionRegistry()
BeanDefinitionRegistryPostProcessor 是 BeanFactoryPostProcessor 的子類,可以通過編碼的方式,改變、新增類的定義,甚至刪除某些 bean 的定義
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
//把定義的mapper包下的所有bean註冊到Spring容器中
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
總結
Spring載入MyBatis這個過程,其實就是把MyBatis的Mapper轉換成Bean,注入到Spring容器的過程。學習這些內容不只是幫助我們瞭解MyBatis是怎麼樣結合Spring的,也讓我們瞭解或許大多數框架結合Spring就是這樣的流程。