本章經過分析 mybatis-spring-x.x.x.jar Jar 包中的源碼,瞭解 MyBatis 是如何與 Spring 進行集成的。spring
MyBatis 與 Spring 集成,在 Spring 配置文件中配置了數據源、SqlSessionFactory、自動掃描 MyBatis 中的 Mapper 接口、事務管理等,這部份內容都交由 Spring 管理。部分配置內容以下所示:sql
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" > <!-- 配置數據源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" lazy-init="true" init-method="init" destroy-method="close"> <property name="url" value="${dataSource.url}" /> <property name="username" value="${dataSource.username}" /> <property name="password" value="${dataSource.password}" /> </bean> <!-- 配置SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="mapperLocations" value="classpath*:rabbit/template/mapper/*.xml" /> </bean> <!-- 配置自動掃描全部Mapper接口 --> <bean id="mapperScan" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="rabbit.template.mapper" /> </bean> <!--配置事務管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 激活CGLIB代理功能 --> <aop:aspectj-autoproxy proxy-target-class="true" /> <tx:annotation-driven transaction-manager="transactionManager" /> ...... </beans>
從配置文件中能夠看出,MyBatis 與 Spring 集成後,MyBatis 中的 SqlSessionFactory 對象是由 SqlSessionFactoryBean 建立的。緩存
SqlSessionFactoryBean 類做爲咱們分析源碼的入口,該類實現了 InitializingBean 接口,在 Bean 初始化的時候,會執行 afterPropertiesSet() 方法,最後會調用 buildSqlSessionFactory() 方法建立 SqlSessionFactory。安全
SqlSessionFactoryBean.buildSqlSessionFactory() 方法的具體實現以下:session
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; // 建立Configuration對象 if (this.configuration != null) { configuration = this.configuration; // 省略配置相關代碼 } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { configuration = new Configuration(); // 省略配置相關代碼 } // 配置objectFactory // 根據Spring配置文件,設置Configuration.objectWrapperFactory // 根據Spring配置文件,設置Configuration.objectFactory // 掃描typeAliasesPackage指定的包,併爲其中的類註冊別名 // 爲typeAliases集合中指定的類註冊別名 // 註冊plugins集合中指定的插件 // 掃描typeHandlersPackage指定的包,並註冊其中的TypeHandler // 註冊指定的typeHandlers // 配置databaseIdProvider // 配置緩存 // 調parse()方法解析配置文件 if (xmlConfigBuilder != null) { xmlConfigBuilder.parse(); } // 若是未配置transactionFactory,則默認使用SpringManagedTransactionFactory if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } // 設置environment configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 根據mapperLocations配置,處理映射配置文件以及相應的Mapper接口 if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } // 調用SqlSessionFactoryBuilder.build()方法,建立SqlSessionFactory對象並返回 return this.sqlSessionFactoryBuilder.build(configuration); }
SqlSessionFactoryBuilder.build() 方法返回的對象類型是 DefaultSqlSessionFactory。mybatis
SqlSessionFactoryBean 同時實現了 FactoryBean 接口,重寫了 getObject() 方法,該方法返回 DefaultSqlSessionFactory 對象。架構
當配置文件中 <bean> 的 class 屬性配置的實現類是 FactoryBean 時,經過 getBean() 方法返回的不是 FactoryBean 自己,而是 FactoryBean#getObject() 方法所返回的對象,至關於FactoryBean#getObject() 代理了 getBean() 方法。app
SqlSessionFactoryBean#getObject() 方法的具體實現以下:框架
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
在《MyBatis 技術內幕》這本書的4.2.4章詳細描述了 mybatis-spring Jar 包中的 SpringManagedTransaction、SqlSessionTemplate、SqlSessionDaoSupport、MapperFactoryBean、MapperScannerConfigurer 類的做用,有不明白的能夠參考。ide
接下來繼續介紹 Spring 配置文件中的 MapperScannerConfigurer Bean 對象。
MapperScannerConfigurer 類實現了 BeanDefinitionRegistryPostProcessor 接口,該接口中的 postProcessBeanDefinitionRegistry() 方法會在系統初始化的過程當中被調用,該方法掃描了配置文件中配置的basePackage 下的全部 Mapper 類,最終生成 Spring 的 Bean 對象,註冊到容器中。
postProcessBeanDefinitionRegistry() 方法具體實現以下:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } // 建立ClassPathMapperScanner對象 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); // 根據上面的配置,生成相應的過濾器。這些過濾器在掃描過程當中會過濾掉不符合添加的內容,例如, // annotationClass字段不爲null時,則會添加AnnotationTypeFilter過濾器,經過該過濾器 // 實現只掃描annotationClass註解標識的接口的功能 scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
ClassPathMapperScanner.scan() 方法會調用父類的 scan() 方法,核心邏輯在 doScan() 方法中實現,具體實現以下:
public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 調用父類的doScan()方法,遍歷basePackages中指定的全部包,掃描每一個包下的Java文件並進行解析。 // 使用以前註冊的過濾器進行過濾,獲得符合條件的BeanDefinitionHolder對象 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 { // 處理掃描獲得的BeanDefinitionHolder集合 processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
ClassPathMapperScanner.processBeanDefinitions() 方法會對 doScan() 方法中掃描到的 BeanDefinition 集合進行修改,主要是將其中記錄的接口類型改造爲 MapperFactoryBean 類型,並填充 MapperFactoryBean 所需的相關信息。
ClassPathMapperScanner.processBeanDefinitions() 方法具體實現以下:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } // 將掃描到的接口類型做爲構造方法的參數 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); // 將BeanDefinition中記錄的Bean類型修改成MapperFactoryBean definition.setBeanClass(this.mapperFactoryBean.getClass()); // 構造MapperFactoryBean的屬性,將sqlSessionFactory、sqlSessionTemplate // 等信息填充到BeanDefinition中 // 修改自動注入方式 } }
ClassPathMapperScanner 在處理 Mapper 接口的時候用到了 MapperFactoryBean 類,它是 MyBatis-Spring 提供的一個動態代理的實現,能夠直接將 Mapper 接口注入到 Service 層的 Bean 中,這樣就不須要編寫任何 DAO 實現的代碼。
MapperFactoryBean 的繼承關係如圖所示:
MapperFactoryBean 類的動態代理功能是經過實現了 Spring 提供的 FactoryBean 接口實現的,該接口是一個用於建立 Bean 對象的工廠接口,經過 geObject() 方法獲取真實的對象。
MapperFactoryBean.geObject() 方法的實現以下:
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
經過 SqlSession 對象獲取 Mapper 文件,《MyBatis 源碼篇-SQL 執行的流程》介紹過 Mapper 接口的代理對象的獲取過程。
getSqlSession() 是 SqlSessionDaoSupport 中的方法。該類的 sqlSession 屬性是經過 Spring 容器自動注入 sqlSessionFactory 屬性實現的。源碼實現以下:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }
這樣就不難理解,在 Spring 容器中,Mapper 接口對應的實現類仍是 MyBatis 提供的 MapperProxy 代理對象。MapperFactoryBean 類只不過是包裝了一下,讓真正的對象可以注入到 Spring 容器中。因此 Mapper 接口對應的實現類是做爲單例一直存在 Spring 容器中的。
由於引入了 Spring 框架,增長了這塊的源碼的理解難度,下面經過一個簡單的時序圖總結 Mapper 實現類的生成過程。
SqlSessionTemplate 實現了 SqlSession 接口,在 MyBatis 與 Spring 集成開發時,用來代替 MyBatis 中的 DefaultSqlSession 的功能。
SqlSessionTemplate 是線程安全的,能夠在 DAO 之間共享使用,好比上面生成的 Mapper 對象會持有一個 SqlSessionTemplate 對象,每次請求都會共用該對象。在 MyBatis 中 SqlSession 的 Scope 是會話級別,請求結束後須要關閉本次會話,怎麼集成了 Spring 後,能夠共用了?
首先,在集成 Spring 後,Mapper 對象是單例,由 Spring 容器管理,供 Service 層使用,SqlSessionTemplate 在設計的時候,功能分紅了以下兩部分:
兩類方法的具體實現以下:
@Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } @Override public <T> T selectOne(String statement) { return this.sqlSessionProxy.<T> selectOne(statement); }
getMapper 方法是能夠共享的,SQL 操做的相關方法是會話級別的不能共享,因此在 SqlSessionTemplate 類中經過動態代理的方式來區分這兩部分功能的實現。
接下來看看 SqlSessionTemplate 類中的動態代理部分的實現:
// SqlSession 的代理類 private final SqlSession sqlSessionProxy; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; // 建立SqlSession的代理類,給sqlSessionProxy屬性賦值 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } // 代理類 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 經過SqlSessionUtils.getSqlSession()獲取SqlSession對象,同一個事務共享SqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); // 調用SqlSession對象的相應方法 Object result = method.invoke(sqlSession, args); // 檢測事務是否由Spring進行管理,並據此決定是否提交事務 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } }
MyBatis 源碼篇