1. 程式人生 > >Mybatis原始碼分析之Spring與Mybatis整合MapperScannerConfigurer處理過程原始碼分析

Mybatis原始碼分析之Spring與Mybatis整合MapperScannerConfigurer處理過程原始碼分析

        前面文章分析了這麼多關於Mybatis原始碼解析,但是我們最終使用的卻不是以前面文章的方式,編寫自己mybatis_config.xml,而是最終將配置融合在spring的配置檔案中。有了前面幾篇部落格的分析,相信這裡會容易理解些關於Mybatis的初始化及其執行,但是仍舊需要Spring的很多知識,用到的時候會簡略提到下。下面先看下我們具體使用Mybatis時候是怎樣配置的。

<?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>  
這就是使用過程中的關於資料庫相關的一個配置檔案。裡面主要涉及到下面兩個Spring的初始化類。

  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前面加一個&符號來獲取

 public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }
      可以看到這個方法返回的是一個sqlSessionFactory(相信前面mybatis初始化的時候對這個類比較熟悉了),這個方法的邏輯也比較簡單,如果這個屬性沒有初始化的化先執行初始化,初始化了直接返回,和假單例模式一樣。

    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的原始碼分析。