终结篇 MyBatis 解析 (三)-- 初始化机制

任何框架的初始化,无非是加载自己运行时所需要的配置信息。MyBatis的配置信息,大概包含以下信息,其高层级结构如下:
这里写图片描述

MyBatis采用了一个非常直白和简单的方式—使用 org.apache.ibatis.session.Configuration对象作为一个所有配置信息的容器,Configuration对象的组织结构和XML配置文件的组织结构几乎完全一样(当然,Configuration对象的功能并不限于此,它还负责创建一些MyBatis内部使用的对象,如Executor等,这将在后续的文章中讨论)。如下图所示:
这里写图片描述

可以这么说,MyBatis初始化的过程,就是创建 Configuration对象的过程。

MyBatis的初始化可以有两种方式

  1. 基于XML配置文件:基于XML配置文件的方式是将MyBatis的所有配置信息放在XML文件中,MyBatis通过加载并XML配置文件,将配置文信息组装成内部的Configuration对象。

  2. 基于Java API:这种方式不使用XML配置文件,需要MyBatis使用者在Java代码中,手动创建Configuration对象,然后将配置参数set 进入Configuration对象中。

mybatis初始化
String strFilePath = Thread.currentThread().getContextClassLoader().getResource("mybatisconfig.xml").toString();
strFilePath = strFilePath.replace("file:/", "");
FileInputStream inputStream = new FileInputStream(strFilePath);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = factory.openSession();

初始化过程中,涉及到了以下几个对象:

  1. SqlSessionFactoryBuilder :SqlSessionFactory的构造器,用于创建SqlSessionFactory,采用了Builder设计模式
  2. Configuration :该对象是mybatis-config.xml文件中所有mybatis配置信息
  3. SqlSessionFactory:SqlSession工厂类,以工厂形式创建SqlSession对象,采用了Factory工厂设计模式
  4. XmlConfigParser :负责将mybatis-config.xml配置文件解析成Configuration对象,共SqlSessonFactoryBuilder使用,创建SqlSessionFactory

创建Configuration对象的过程:

接着上述的 MyBatis初始化基本过程讨论,当SqlSessionFactoryBuilder执行build()方法,调用了XMLConfigBuilder的parse()方法,然后返回了Configuration对象。那么parse()方法是如何处理XML文件,生成Configuration对象的呢?

生成Configuration对象主要包括以下两大步:

  1. XMLConfigBuilder解析mybatis-config.xml的配置到Configuration中
  2. XMLMapperBuilder解析Mapper配置文件的配置到Configuration中
1. 解析mybatis-config.xml

(1)XMLConfigBuilder会将XML配置文件的信息转换为Document对象,而XML配置定义文件DTD转换成XMLMapperEntityResolver对象,然后将二者封装到XpathParser对象中,XpathParser的作用是提供根据Xpath表达式获取基本的DOM节点Node信息的操作。如下图所示

这里写图片描述

(2)之后XMLConfigBuilder调用parse()方法:会从XPathParser中取出 < configuration>节点对应的Node对象,然后解析此Node节点的子Node:properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers:

public Configuration parse() {  
     if (parsed) {  
         throw new BuilderException("Each XMLConfigBuilder can only be used once.");  
     }  
     parsed = true;  
     //源码中没有这一句,只有parseConfiguration(parser.evalNode("/configuration"));  
     //为了让读者看得更明晰,源码拆分为以下两句  
     XNode configurationNode = parser.evalNode("/configuration");  
     parseConfiguration(configurationNode);  
     return configuration;
}
/** 
  * 解析 "/configuration"节点下的子节点信息,然后将解析的结果设置到Configuration对象中 
  */  
private void parseConfiguration(XNode root) {  
     try {  
         //1.首先处理properties 节点     
         propertiesElement(root.evalNode("properties")); //issue #117 read properties first  
         //2.处理typeAliases  
         typeAliasesElement(root.evalNode("typeAliases"));  
         //3.处理插件  
         pluginElement(root.evalNode("plugins"));  
         //4.处理objectFactory  
         objectFactoryElement(root.evalNode("objectFactory"));  
         //5.objectWrapperFactory  
         objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));  
         //6.settings  
         settingsElement(root.evalNode("settings"));  
         //7.处理environments  
         environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631  
         //8.database  
         databaseIdProviderElement(root.evalNode("databaseIdProvider"));  
         //9.typeHandlers  
         typeHandlerElement(root.evalNode("typeHandlers"));  
         //10.mappers  
         mapperElement(root.evalNode("mappers"));  
     } catch (Exception e) {  
         throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);  
     }  
 }

注意:在上述代码中,还有一个非常重要的地方,就是解析XML配置文件子节点< mappers>的方法mapperElements(root.evalNode(“mappers”)), 它将解析我们配置的Mapper.xml配置文件,Mapper配置文件可以说是MyBatis的核心,MyBatis的特性和理念都体现在此Mapper的配置和设计上。

我们就看上述的environmentsElement(root.evalNode(“environments”)); 方法是如何将environments的信息解析出来,设置到Configuration对象中的:

/** 
  * 解析environments节点,并将结果设置到Configuration对象中 
  * 注意:创建envronment时,如果SqlSessionFactoryBuilder指定了特定的环境(即数据源); 
  *      则返回指定环境(数据源)的Environment对象,否则返回默认的Environment对象; 
  *      这种方式实现了MyBatis可以连接多数据源 
  */  
private void environmentsElement(XNode context) throws Exception {  
    if (context != null)  
    {  
         if (environment == null)  
         {  
             environment = context.getStringAttribute("default");  
         }  
         for (XNode child : context.getChildren())  
         {  
              String id = child.getStringAttribute("id");  
              if (isSpecifiedEnvironment(id))  
              {  
                  //1.创建事务工厂 TransactionFactory  
                  TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));  
                  DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));  
                  //2.创建数据源DataSource  
                  DataSource dataSource = dsFactory.getDataSource();  
                  //3. 构造Environment对象  
                  Environment.Builder environmentBuilder = new Environment.Builder(id)  
             .transactionFactory(txFactory)  
             .dataSource(dataSource);  
                  //4. 将创建的Envronment对象设置到configuration 对象中  
                  configuration.setEnvironment(environmentBuilder.build());  
             }  
         }  
    }  
}
private boolean isSpecifiedEnvironment(String id)  
{  
      if (environment == null)  
      {  
           throw new BuilderException("No environment specified.");  
      }  
      else if (id == null)  
      {  
           throw new BuilderException("Environment requires an id attribute.");  
      }  
      else if (environment.equals(id))  
      {  
          return true;  
      }  
      return false;  
 }
2. XMLMapperBuilder解析Mapper

一个Mapper的配置文件最终会由XMLMapperBuilder对象解析加载到Configuration对象中。

private void mapperElement(XNode parent) throws Exception {  
   if (parent != null) {  
     for (XNode child : parent.getChildren()) {  
       if ("package".equals(child.getName())) {  
         String mapperPackage = child.getStringAttribute("name");  
         configuration.addMappers(mapperPackage);  
       } else {  
         String resource = child.getStringAttribute("resource");  
         String url = child.getStringAttribute("url");  
         String mapperClass = child.getStringAttribute("class");  
         if (resource != null && url == null && mapperClass == null) {  
           ErrorContext.instance().resource(resource);  
           InputStream inputStream = Resources.getResourceAsStream(resource);  
           //由XMLMapperBuilder对象解析加载  
           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());  
           mapperParser.parse();  
         } else if (resource == null && url != null && mapperClass == null) {  
           ErrorContext.instance().resource(url);  
           InputStream inputStream = Resources.getUrlAsStream(url);  
           //由XMLMapperBuilder对象解析加载  
           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());  
           mapperParser.parse();  
         } else if (resource == null && url == null && mapperClass != null) {  
           Class<?> mapperInterface = Resources.classForName(mapperClass);  
           configuration.addMapper(mapperInterface);  
         } else {  
           throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");  
         }  
       }  
     }  
   }  
 }  

将上述的MyBatis初始化基本过程的序列图细化:
这里写图片描述

转自: https://www.jianshu.com/p/ec40a82cae28

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值