mybatis源碼分析一(加載配置文件)

最近一直在看mybatis源碼,稍有心得,接下來(lái)就然我們一起看下springboot整合mybatis的源碼的步驟是怎樣的

廢話不多說(shuō),咱們就一起看看源碼吧
首先,咱們看下配置文件,下面是我配置的配置文件,沒(méi)什么多說(shuō)的,都是基本配置,映射文件的位置,實(shí)體類的位置,數(shù)據(jù)庫(kù)的基本信息等
之前一直有個(gè)疑問(wèn),就是咱們?cè)谂渲梦募袑?xiě)這些配置的時(shí)候都會(huì)自動(dòng)提示,一直不知道是怎么回事,看了源碼,才知道原因,原來(lái),在
spring-configuration-metadata.json這個(gè)json文件里面配置了相對(duì)應(yīng)的屬性,MybatisProperties與之相對(duì)應(yīng),咱們經(jīng)常配置的屬性有以下幾個(gè)分別為

mybatis

//檢查配置文件是否存在的開(kāi)關(guān)(如果確實(shí)沒(méi)有配置mybatis的配置文件,這個(gè)就可以不用配置了)
mybatis.check-config-location=true
//映射文件的地址
mybatis.mapper-locations=classpath:/mapper/*.xml
//實(shí)體類的地址
mybatis.type-aliases-package=com.ryx.xiaoxin_distribute.entity.pojo

datasource

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://xx.xx.xx.xx:3306/ryx?autoReconnect=true&useUnicode=true&useSSL=false&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root

當(dāng)咱們啟動(dòng)springboot項(xiàng)目的時(shí)候,會(huì)先進(jìn)入run方法(今天咱們主要分析的mybatis的源碼流程,所以,spring的源碼就先不分析了),
mybatis的加載的主要的類有 MybatisAutoConfiguration,在這個(gè)類中,spring會(huì)完成SqlSessionFactory的創(chuàng)建,SqlSessionTemplate的
簡(jiǎn)單敘述下這個(gè)流程
第一步:啟動(dòng)run方法
第二步:執(zhí)行refreshContext方法,刷新上下文
第三步:執(zhí)行prepareRefresh方法執(zhí)行預(yù)刷新
第四步:invokeBeanFactoryPostProcessors(beanFactory)注冊(cè)bean工廠
第五步:finishBeanFactoryInitialization(beanFactory);實(shí)例化所有的單例
第六步:遍歷每一個(gè)bean實(shí)例,這里會(huì)獲取到MybatisAutoConfiguration這個(gè)實(shí)例,然后調(diào)用getBean方法,
第七步:執(zhí)行這里會(huì)獲取到MybatisAutoConfiguration類中的checkConfigFileExists檢查文件是否存在
第八步:創(chuàng)建SqlSessionFactory
第九步:創(chuàng)建SqlSessionTemplate,這里值得注意的是在MybatisAutoConfiguration中有一個(gè)靜態(tài)內(nèi)部類AutoConfiguredMapperScannerRegistrar
用于掃描加了@Mapper注解的接口,也就是說(shuō),如果在項(xiàng)目啟動(dòng)的時(shí)候沒(méi)有配置mapperSan注解,需要在數(shù)據(jù)庫(kù)接口上添加這個(gè)注解,區(qū)別在于@MapperSan
是一個(gè)全局掃描注解,只要指定包名,就能全部加載,然而@Mapper需要在每個(gè)操作數(shù)據(jù)庫(kù)的接口上都要添加@Mappe接口.源碼執(zhí)行的時(shí)候就會(huì)看到效果,當(dāng)在
springboot啟動(dòng)類型添加了@MapperSan注解,是不會(huì)進(jìn)入這個(gè)方法的,但當(dāng)沒(méi)有配置的時(shí)候,是會(huì)執(zhí)行這個(gè)方法的

MybatisAutoConfiguration中最主要的有三個(gè)方法
1:checkConfigFileExists:檢查文件是否存在
2:創(chuàng)建SqlSessionFactory
3:創(chuàng)建SqlSessionTemplate

接下來(lái),一起來(lái)分析下這三個(gè)方法,

checkConfigFileExists
@PostConstruct
public void checkConfigFileExists() {
//檢查屬性文件是否存在
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(), "Cannot find config location: " + resource
+ " (please add config file or check your Mybatis configuration)");
}
}

//創(chuàng)建SqlSessionFactory對(duì)象,在創(chuàng)建SqlSessionFactory對(duì)象的同時(shí),會(huì)將咱們配置在配置文件中的各種屬性設(shè)置到SqlSessionFactoryBean中
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
//創(chuàng)建SqlSessionFactoryBean對(duì)象
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
//向SqlSessionFactoryBean設(shè)置屬性值
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
//向factory中添加配置文件屬性
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
//添加實(shí)體類的類路徑
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
//添加映射文件地址
factory.setMapperLocations(this.properties.resolveMapperLocations());
}

    //獲取factory對(duì)象,一起看下這個(gè)方法
  return factory.getObject();
}

 @Override
  public SqlSessionFactory getObject() throws Exception {
     //當(dāng)SqlSessionFactory為空,調(diào)用afterPropertiesSet構(gòu)建SqlSessionFactory
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    //不為空,直接返回
    return this.sqlSessionFactory;
  }

重點(diǎn)看下afterPropertiesSet,就是facory為空的時(shí)候需要構(gòu)建factory
@Override
public void afterPropertiesSet() throws Exception {
//dataSource不能為空
notNull(dataSource, "Property 'dataSource' is required");
//sqlSessionFactoryBuilder不能為空
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
//configuration和configLocation的屬性不能一起設(shè)置
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//構(gòu)建sqlSessionFacory
this.sqlSessionFactory = buildSqlSessionFactory();
}

//終于快到重點(diǎn)了,buildSqlSessionFactory,一起來(lái)看看這個(gè)方法,在這個(gè)方法的創(chuàng)建中,會(huì)加載配置文件,然后加載映射文件

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

  Configuration configuration;
  //構(gòu)建xmlConfigBuilder對(duì)象
  XMLConfigBuilder xmlConfigBuilder = null;
  //校驗(yàn)configuration和configLocation,并且向configuration中添加屬性
  if (this.configuration != null) {
    configuration = this.configuration;
    if (configuration.getVariables() == null) {
      configuration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      configuration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    configuration = xmlConfigBuilder.getConfiguration();
  } else {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    }
    configuration = new Configuration();
    if (this.configurationProperties != null) {
      configuration.setVariables(this.configurationProperties);
    }
  }
    //設(shè)置對(duì)象工廠屬性
  if (this.objectFactory != null) {
    configuration.setObjectFactory(this.objectFactory);
  }
    //向configuration設(shè)置對(duì)象包裝工廠
  if (this.objectWrapperFactory != null) {
    configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  }

  if (this.vfs != null) {
    configuration.setVfsImpl(this.vfs);
  }
    //解析類型別名包,就是咱們?cè)趯傩晕募信渲玫膒ojo類
  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 (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
    try {
      configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  if (this.cache != null) {
    configuration.addCache(this.cache);
  }

    //開(kāi)始解析mybatis的配置文件
  if (xmlConfigBuilder != null) {
    try {
       //調(diào)用xmlConfigBuilderpare方法執(zhí)行配置文件加載
      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();
    }
  }

    //創(chuàng)建事物工廠對(duì)象
  if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
  }

  configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    //解析映射文件
  if (!isEmpty(this.mapperLocations)) {
    for (Resource mapperLocation : this.mapperLocations) {
      if (mapperLocation == null) {
        continue;
      }

      try {
        //循環(huán)加載所有的映射文件
        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);
}

//終于要進(jìn)入解析配置文件的方法了,這個(gè)步驟有點(diǎn)艱難啊,是不是,哈哈
我們進(jìn)入xmlConfigBuilder.parse();方法,一起來(lái)看下


//注意一個(gè) xpath 表達(dá)式 - /configuration。這個(gè)表達(dá)式代表的是 MyBatis 的<configuration/>標(biāo)簽,這里選中這個(gè)標(biāo)簽,并傳遞給parseConfiguration方法
 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //解析propertie配置
      propertiesElement(root.evalNode("properties"));
      //解析settings配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //加載vfs
      loadCustomVfs(settings);
      //解析typeAliases配置
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析plugins配置
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory配置
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory配置
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析 reflectorFactory 配置
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //將settings中的信息添加到configurtion對(duì)象中
      settingsElement(settings);
      // 解析 environments 配置
      environmentsElement(root.evalNode("environments"));
      //解析 databaseIdProvider,獲取并設(shè)置 databaseId 到 Configuration 對(duì)象
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析 typeHandlers 配置
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析 mappers 配置
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

//終于要進(jìn)入解析配置文件的方法了,這個(gè)步驟有點(diǎn)艱難啊,是不是,哈哈
我們進(jìn)入xmlConfigBuilder.parse();方法,一起來(lái)看下

//注意一個(gè) xpath 表達(dá)式 - /configuration。這個(gè)表達(dá)式代表的是 MyBatis 的<configuration/>標(biāo)簽,這里選中這個(gè)標(biāo)簽,并傳遞給parseConfiguration方法
 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

      先來(lái)看看properties屬性的解析

private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 解析 propertis 的子節(jié)點(diǎn),并將這些節(jié)點(diǎn)內(nèi)容轉(zhuǎn)換為屬性對(duì)象 Properties
Properties defaults = context.getChildrenAsProperties();
//獲取屬性中resource和url的屬性值
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//兩個(gè)都不為空,拋出異常
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
//從文件中加載并解析屬性文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//通過(guò)url解析屬性文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 將屬性值設(shè)置到 configuration 中
configuration.setVariables(defaults);
}
}
解析 settings 配置
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 獲取 settings 子節(jié)點(diǎn)中的內(nèi)容
Properties props = context.getChildrenAsProperties();

// 創(chuàng)建 Configuration 類的對(duì)象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
    // 檢測(cè) Configuration 中是否存在相關(guān)屬性,不存在則拋出異常
    if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
    }
}
return props;

}
暫時(shí)分析到這里吧,其余的我就不做分析了,這塊代碼比較多,小伙伴們可以自行分析下這塊的內(nèi)容,
下一期咱們一起分析下對(duì)映射文件的解析!
Thanks!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容