Mybatis原始碼分析之Spring與Mybatis整合MapperScannerConfigurer處理過程原始碼分析
前面文章分析了這麼多關於Mybatis原始碼解析,但是我們最終使用的卻不是以前面文章的方式,編寫自己mybatis_config.xml,而是最終將配置融合在spring的配置檔案中。有了前面幾篇部落格的分析,相信這裡會容易理解些關於Mybatis的初始化及其執行,但是仍舊需要Spring的很多知識,用到的時候會簡略提到下。下面先看下我們具體使用Mybatis時候是怎樣配置的。
這就是使用過程中的關於資料庫相關的一個配置檔案。裡面主要涉及到下面兩個Spring的初始化類。<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <!-- 引入配置檔案 --> <!-- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化連線大小 --> <property name="initialSize" value="${jdbc.initialSize}"></property> <!-- 連線池最大數量 --> <property name="maxActive" value="${jdbc.maxActive}"></property> <!-- 連線池最大空閒 --> <property name="maxIdle" value="${jdbc.maxIdle}"></property> <!-- 連線池最小空閒 --> <property name="minIdle" value="${jdbc.minIdle}"></property> <!-- 獲取連線最大等待時間 --> <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置對映檔案 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描mapping.xml檔案 --> <property name="mapperLocations" value="classpath*:com/yzh/mapping/*.xml"/> <property name="configurationProperties"> <props> <prop key="dialect">mysql</prop> </props> </property> <property name="plugins"> <array> <ref bean="pageInterceptor"/> </array> </property> </bean> <!-- mybatis分頁攔截器 --> <bean id = "pageInterceptor" class = "com.yzh.util.PageInterceptor"></bean> <!-- DAO介面所在包名,Spring會自動查詢其下的類 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.yzh.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
SqlSessionFactoryBean類
負責Mybatis的初始化,最終會初始化出一個Configuration
MapperScannerConfigurer類
負責建立介面的代理,可以看到上面類例項化的時候傳入的引數就是介面包
下面開始原始碼的具體分析。
(1)SqlSessionFactoryBean
從上面的配置檔案可以看到這個類有幾個屬性,dataSource,plugin都比較熟悉,就不說了。主要講解mapper.xml檔案的初始化。
看一下這個類的繼承結構
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
實現了三個介面,簡要分析一下這三個介面。
1.FactoryBean
工廠bean,例項化的時候不是返回物件本身,而是呼叫它的方法getObject()方法返回的物件,如果要獲取FactoryBean物件,可以在id前面加一個&符號來獲取
可以看到這個方法返回的是一個sqlSessionFactory(相信前面mybatis初始化的時候對這個類比較熟悉了),這個方法的邏輯也比較簡單,如果這個屬性沒有初始化的化先執行初始化,初始化了直接返回,和假單例模式一樣。public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
2.InitailizingBean
實現這個介面的bean,會在其例項化完成後,初始化階段呼叫他的方法afterPropertiesSet()(可以看下spring bean的生命週期)
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();
}
可以看到這個方法的邏輯程式碼就是最後一句,也是從這一句開啟了mapper.xml的初始化,這也就是mybatis和spirng結合以後初始化的入口,後面具體分析。
3.ApplicationListener
是一個介面,裡面只有一個onApplicationEvent方法。所以自己的類在實現該介面的時候,要實裝該方法。如果在上下文中部署一個實現了ApplicationListener介面的bean,那麼每當在一個ApplicationEvent釋出到 ApplicationContext時,這個bean得到通知。其實這就是標準的Oberver設計模式。
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
這個不是分析的重點,有興趣的自行查下。
下面從初始化入口開啟分析:
this.sqlSessionFactory = buildSqlSessionFactory();
還記得前面一篇初始化配置檔案為Configuration類的部落格嗎,整過過程切實初始化出來就是構建了SqlSessionFactory,SqlSessionFactory裡包含Configuration。看這個方法的名字也是做的這件事,只是入口的方式不一樣,下面看看是怎樣歸到一起的。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (logger.isDebugEnabled()) {
logger.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (logger.isDebugEnabled()) {
logger.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (logger.isDebugEnabled()) {
logger.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (logger.isDebugEnabled()) {
logger.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
if (this.databaseIdProvider != null) {
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
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");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
方法看起來有點長,其實邏輯很簡單,就是將配置檔案中的各個配置屬性讀入進來,因為它得對每個屬性判空,所以顯得很長,其實執行的只是配置了屬性的那幾項。
分析一下這個方法的邏輯,第一個if語句,調到了else分支例項化了一個Configuration類出來,下一步將資料庫的初始化讀入進來。
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
接著就到了解析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 + "'");
}
}
}
mapperLocations可以以一個數組的方式將所有xml檔案配置過來,然後逐步解析。這裡其實就已經歸到了前面部落格的初始化過程了,後面就是逐個解析xml檔案,解析每一個節點,將每個sql節點初始化為一個MappedStatement類,最終歸入到Configuration裡。初始化就講解這麼多。
(2) MapperScannerConfigurer
下面看這個類是怎麼將介面建立為代理的。首先也看下這個類的繼承結構。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
1.Aware介面
實現這些 Aware介面的Bean在被例項化之後,可以取得一些相對應的資源,例如實現BeanFactoryAware的Bean在例項化後,Spring容器將會注入BeanFactory的例項,而實現ApplicationContextAware的Bean,在Bean被例項化後,將會被注入
ApplicationContext的例項等等。
2.InitalizingBean
public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}
沒有什麼邏輯,僅僅是一個對basePackage的判空操作3.BeanDefinitionRegistryPostProcessor
這個介面:public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor。
實現該介面,可以在spring的bean建立之前,修改bean的定義屬性。也就是說,Spring允許BeanFactoryPostProcessor在容器例項化任何其它bean之前讀取配置元資料,並可以根據需要進行修改,例如可以把bean的scope從singleton改為prototype,也可以把property的值給修改掉。可以同時配置多個BeanFactoryPostProcessor,並通過設定'order'屬性來控制各個BeanFactoryPostProcessor的執行次序。注意:BeanFactoryPostProcessor是在spring容器載入了bean的定義檔案之後,在bean例項化之前執行的。
這個類的執行就是從這個後處理開始的:
/**
*
* @param registry 這個引數是spring註冊beanDefiniton的地方,這個類裡面有一個快取map,專門儲存beanDefinition
* @throws BeansException
*/
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//解決${jdbc.username}這種佔位符的複製問題
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
//這個類就是將介面掃描為BeanDefinition,並且最終被代理
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();
//掃描器的scan方法將basePackage掃描為beanDefinition
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
感覺理解Mybatis的知識還是得具備一些spring原始碼的知識,不然略微會有點不知所云,至少應該知道spring bean的生命週期,BeanDefinition的感念。
//邏輯轉移到了doScan方法
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
這裡的doScan方法呼叫的是ClassPathMapperScanner中的,這裡算是類的多型特性。
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//首先呼叫了父類的doScan方法,下面具體分析,比較關鍵,轉化為了spring bean的形式
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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
//這裡就轉化為了正常的spring中的bean的形式了,spring就是最開始將配置檔案初始化為了一個GenericBeanDefinition
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//這種就是對MapperFactoryBean中的一些屬性的賦值,(可以看些BeanWrapperImpl的相關知識)
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
//最最關鍵的一步,這就是beanDefintion的真正的型別,這裡也就是將所有的介面類最終都按照MapperFactoryBean處理的
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
//在MapperFactoryBean中找了一下sqlSessionFactory這個屬性,沒有找到,他的父類SqlSessionDaoSupport中也沒有這個屬性
//但是有sqlSession這個屬性,其實這裡給sqlSessionFactory賦值,就是呼叫他的set方法,看到這個類雖然沒有這個屬性,但是確實有這個
//屬性的set方法,玄機都在這個 set方法裡,最終跟蹤下去例項化了蠻多物件
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
上面的方法特別注意兩個地方,將所有的介面轉化為MapperFactoryBean和為sqlSessionFactory賦值。這個方法結束以後就完成了將介面註冊成為了spring中真正的bean了,但是還沒有經歷例項化,例項化的過程中會對其進行代理。//看返回值就看得出,將傳入的dao介面,最終掃描為BeanDefinitionHolder,這個類裡包含這BeanDefinition屬性
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
//顯然basePackages也是可以配置為陣列形式的
for (String basePackage : basePackages) {
//靠這個函式就將basePackage轉化為最基本的beanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
//過濾處理所有生成的基本beanDefinition
for (BeanDefinition candidate : candidates) {
//bean是單例還是原型判斷
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
//取得beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
//封裝到holder類中
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//註冊到registry中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
//basePackage路徑構造
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
//將路徑轉化為資源
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
//遍歷每一個檔案資源
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
//對資源內容的一個轉化
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
//用傳入的資源構建出基本的BeanDefinition,初始化bean的名稱,註解等一些基本欄位
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
到這裡就從細節上完成了介面註冊為bean的過程了,下面所有的動作就只能在MapperFactoryBean裡了,下面詳細分析這個類。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means
* it must have been included in mybatis-config.xml.
* <p>
* If it is true, the mapper will be added to MyBatis in the case it is not already
* registered.
* <p>
* By default addToCofig is true.
*
* @param addToConfig
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* {@inheritDoc}
*/
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
public boolean isSingleton() {
return true;
}
}
相信大家也看出來了這個類上的玄機,繼承了一個類,同時實現了FactoryBean。首先看下繼承的這個類,
public abstract class SqlSessionDaoSupport extends DaoSupport,是一個抽象類,同時又繼承了另一個類
感覺以這種截圖的方式講解還蠻清晰的。看到這個類是不是明白了,最終相當於MapperFactoryBean實現了InitializingBean,看到afterProperties方法中呼叫了一個抽象方法,這個方法實在哪個子類中實現的呢?
mybatis與sping結合的所有初始化就結束了,到這裡不僅早就有了Configuration類,sqlSession也被初始化完成了,下面就是用sqlSession呼叫執行方法的起始,getMapper的時候了,那麼這個是怎麼處理的呢,上面看到MapperFactoryBean還繼承了工廠bean,所以這個bean被例項化的時候會呼叫他的getObeject方法,在這個方法中移花接木,生成了代理。
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
其實只有這麼一句,不過也看到了通過介面,呼叫getMapper方法了,這樣就又歸到了前面部落格執行介面方法的流程裡了,在這個方法呼叫完最終返回的是一個傳入介面的動態代理。
-------------------------------------------------------------------------------20170910凌晨2:09的分割線----------------------------------------------------------------------------------
沒什麼想說的,其實也沒有想象中的那麼邏輯複雜,只是需要一點spring原始碼的知識。下一步期望可以分析一下Mybatis的sqlNode的問題,感覺這裡還是不行,還有就是下一個系列SpringMVC的原始碼分析。