深入浅出Mybatis源码解析——全局配置文件解析加载流程

前言

前面笔者其实已经写了两篇关于Mybatis源码解析的文章,可后面自觉其解析的不够全面,但是那两篇文章完全是按照debug模式,简单的走了一遍流程去解析的,对于Mybatis内部的很多类的属性以及类的作用都没有做相关介绍。后来想想还是觉得好好写几篇关于Mybatis的文章,其实这也是为了强逼自己要深度的去阅读一下源码。

现在写这篇文章的同时,也是笔者自己在学习的一个过程,如果文章中有错误之处,也欢迎同学们纠正。由于前面的文章已经准备了一些源码阅读环境,所以本次系列里就不以基础的开始了,而是直接开干,如果有不太理解的可以先去阅读一下前面的两篇文章:Mybatis源码解析《一》、Mybatis源码解析《二》,如果觉的这两篇文章还是不够清晰,那么可以去阅读官网:http://www.mybatis.org/mybatis-3/zh/index.html。

已经说了这么多了,那么我们就直接开干吧!在开干之前,笔者先说一句,对于什么阅读源码的思路,笔者由于水平较浅,自认为最好还是不要说一些误人子弟的所谓的方法,笔者只是把自己阅读源码的过程和大家分享一下罢!若有不足之处,也请大家指出。再标注一下,此系列的代码都是基于Mybatis-3.5.0-SNAPSHOT版本的。

 

一、SqlSessionFactoryBuilder类概观

前面已经说了,本系列文章不涉及Mybatis的使用说明,所以这里直接从SqlSessionFactoryBuilder().build(inputStream)说起,从build方法进入SqlSessionFactoryBuilder类,那么便可以看大如下代码:

  // 1.
  public SqlSessionFactory build(InputStream inputStream) {
    // 2.
    return build(inputStream, null, null);
  }


  // 3.
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // XMLConfigBuilder是用来解析XML配置文件的(这里使用了构建者模式)
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

      // 4.
      // parser.parse():使用XPATH语法解析XML配置文件,解析后将配置文件信息封装为Configuration对象中
      // 返回DefaultSqlSessionFactory对象,该对象持有Configuration对象(封装配置文件信息)
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  // 5.
  public SqlSessionFactory build(Configuration config) {
	// 创建SqlSessionFactory接口的默认实现类(传入configuration对象)
    return new DefaultSqlSessionFactory(config);
  }

这个类中的build方法还有其他重载,这里就不贴代码了,从最后一步,就是标签5处可以得知,此类的作用是构建DefaultSqlSessionFactory对象。

从上面代码可以得知这个类执行的大体流程如下:

  1. 调用build(inputStream, null, null)方法。
  2. 构建XMLConfigBuilder对象。
  3. 通过XMLConfigBuilder对象调用parser.parse()来解析XML文件。
  4. 解析后调用build(parser.parse())方法。
  5. 最后构建DefaultSqlSessionFactory对象。

 

二、XMLConfigBuilder 类的构建及相关执行流程

从上面的流程中可以看出,那几步流程中,首先最关键的一步是构建XMLConfigBuilder对象,那就来先看下这个构建的代码:


  // 1.
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  // 2.
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //  创建Configuration对象,并通过TypeAliasRegistry方法注册一些Mybatis内部相关类的别名
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

首先来简单说下这两个构造方法的基本作用:

  • 首先 XMLConfigBuilder(InputStream inputStream, String environment, Properties props)是通过前面所说的new   XMLConfigBuilder(inputStream, environment, properties)来实现的。
    • 在此方法中首先构造了XMLMapperEntityResolver类,这个类的作用是将一个公共的DTD转换为本地的。
    • 然后构造了XPathParser类。
  • 在完成第一步后,再执行XMLConfigBuilder(XPathParser parser, String environment, Properties props)这样的一个内部有参构造函数。
    • 在此构造方法中创建了Configuration对象,同时还初始化内置类的别名。

在未开始正式解析上面代码之前,先来看下XMLConfigBuilder这个类的实现和其中的属性:


public class XMLConfigBuilder extends BaseBuilder {

  // 是否解析
  private boolean parsed;
  // XPathParser 解析类
  private final XPathParser parser;
  private String environment;
  // 获得DefaultReflectorFactory工厂类
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

  // 省略部分代码
  ...........
}

上面的代码中可以看出XMLConfigBuilder是继承于BaseBuilder 类,由此便说明XMLConfigBuilder具有BaseBuilder 的功能(这个后面会有所提及)。

三、XPathParser类的构建和相关执行流程

前面提及构造XPathParser类和创建Configuration对象,那么来看下XPathParser

// XPathParser 解析类
public class XPathParser {

  // Document 对象
  private final Document document;
  // 是否验证
  private boolean validation;
  // 实体解析类
  private EntityResolver entityResolver;
  // 属性对象集合
  private Properties variables;
  // XPath 接口:提供XPath 相关的功能
  private XPath xpath;

  // 省略部分其他构造函数代码

  // 1.
  // XPathParser构造函数
  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    // commonConstructor构造函数
    // 2.
    commonConstructor(validation, variables, entityResolver);
    // 3.
    // 解析XML文档为Document对象
    this.document = createDocument(new InputSource(inputStream));
  }

  // 省略部分代码

  // 3.
  // 解析XML文档为Document对象
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      // 创建DocumentBuilderFactory 实例
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      // 进行dtd或者Schema校验
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      // 设置忽略注释为true
      factory.setIgnoringComments(true);
      // 设置是否忽略元素内容中的空白
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      // 设置扩展实体类引用
      factory.setExpandEntityReferences(true);

      // 获取 DocumentBuilder 对象
      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        // 省略部分代码
      });
      // 通过dom解析,获取Document对象
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

  // 2.
  // commonConstructor构造函数
  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    // 获得 XPathFactory 共厂实例
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

}

这段代码的入口是从前面提及构造XPathParser的代码new XPathParser()开始的,然后进入标记1处的,在这个构造函数中,首先执行commonConstructor()方法,然后执行 createDocument(new InputSource(inputStream))方法来解析全局XML配置文件为document对象(使用XPath语法来解析)。

既然 介绍完了XPathParser类的相关功能了,那么下面继续说下Configuration类吧!

 

四、Configuration类的构建和基本信息

在没开始解析前,首先要说一下,这个configuration对象会贯穿Mybatis的整个执行流程,所以对于它还是要格外的关注一下,那么就来先看下代码,首先看它的构造函数:

    public Configuration() {
                // 注册JdbcTransactionFactory类的别名为JDBC
		typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
                // 注册 ManagedTransactionFactory 类的别名为 MANAGED
		typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

                // 注册 JndiDataSourceFactory 类的别名为JNDI
		typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
                // 注册 Slf4jImpl 类的别名为 SLF4J
		typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
                // 注册 Log4jImpl 类的别名为 LOG4J
		typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
                // 注册 CglibProxyFactory 类的别名为 CGLIB
		typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
                 // 注册 JavassistProxyFactory 类的别名为 JAVASSIST
		typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
        
		languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
		languageRegistry.register(RawLanguageDriver.class);
	}

这个构造函数由于代码数量比较多,所以笔者只选择了一些大家日常比较熟悉的代码进行了注释,其实这个构造函数的作用基本上就是去注册一些类的别名。看完了这些,那继续看下它的一些相关属性。

public class Configuration {

	// 环境类
	protected Environment environment;

	// 这些是一些属性的设置
	protected boolean safeRowBoundsEnabled;
	protected boolean safeResultHandlerEnabled = true;
	protected boolean mapUnderscoreToCamelCase;
	protected boolean aggressiveLazyLoading;
	protected boolean multipleResultSetsEnabled = true;
	protected boolean useGeneratedKeys;
	protected boolean useColumnLabel = true;
	protected boolean cacheEnabled = true;
	protected boolean callSettersOnNulls;
	protected boolean useActualParamName = true;
	protected boolean returnInstanceForEmptyRow;

	protected String logPrefix;
	protected Class logImpl;
	protected Class vfsImpl;
	// 本地本地缓存范围
	protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
	// jdbc 类型设置 
	protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
	// 懒加载触发方法集合
	protected Set lazyLoadTriggerMethods = new HashSet<>(
			Arrays.asList("equals", "clone", "hashCode", "toString"));
	protected Integer defaultStatementTimeout;
	protected Integer defaultFetchSize;
	// 默认执行器类型:SIMPLE
	protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
	// 自动映射行为:PARTIAL(局部)
	protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
	// 自动映射未知列行为
	protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

	// 创建 Properties 属性集合
	protected Properties variables = new Properties();
	// 创建 ReflectorFactory 反射工厂类
	protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
	// 创建 ObjectFactory 对象
	protected ObjectFactory objectFactory = new DefaultObjectFactory();
	// 创建 ObjectWrapperFactory
	protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

	// 懒加载是否启用
	protected boolean lazyLoadingEnabled = false;
	// 创建代理工厂:使用内部的Javassist代替OGNL
	protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

	protected String databaseId;

	protected Class configurationFactory;

	// 映射器注册
	protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
	// 拦截器链
	protected final InterceptorChain interceptorChain = new InterceptorChain();
	// 类型处理器注册
	protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
	protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
	// 数据库语言驱动注册器
	protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

	// MappedStatement 集合
	protected final Map mappedStatements = new StrictMap(
			"Mapped Statements collection")
					.conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource()
							+ " and " + targetValue.getResource());
	// Cache 集合
	protected final Map caches = new StrictMap<>("Caches collection");
	// 结果集集合
	protected final Map resultMaps = new StrictMap<>("Result Maps collection");
	// ParameterMap 集合
	protected final Map parameterMaps = new StrictMap<>("Parameter Maps collection");
	protected final Map keyGenerators = new StrictMap<>("Key Generators collection");
	
	// 资源加载集合
	protected final Set loadedResources = new HashSet<>();
	protected final Map sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
	
	// XMLStatementBuilder 集合
	protected final Collection incompleteStatements = new LinkedList<>();
	protected final Collection incompleteCacheRefs = new LinkedList<>();
	protected final Collection incompleteResultMaps = new LinkedList<>();
	protected final Collection incompleteMethods = new LinkedList<>();

    
	protected final Map cacheRefMap = new HashMap<>();
    
        // 省略部分代码
}

上面代码中的属性不可为庞杂,因此笔者对此只进行了一个简单的注释。这期间的很多属性在整个Mybatis的执行流程中都有涉及。

 

五、XMLConfigBuilder解析方法

前面对整个解析过程中所涉及相关的一些类进行了基本的介绍,那么这里要回到SqlSessionFactoryBuilder类中的parser.parse()方法了,这个parserXMLConfigBuilder中的方法,虽然前面介绍了XMLConfigBuilder类,但是因为还未涉及到解析,所以就没有提及解析的方法。那现在就正式开始介绍解析方法的代码:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 设置属性
    parsed = true;
    // parser.evalNode("/configuration"):通过XPATH解析器,解析configuration根节点
    // 从configuration根节点开始解析,最终将解析出的内容封装到Configuration对象中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  // XPathParser.java
  public XNode evalNode(String expression) {
	// 根据XPATH语法,获取指定节点
    return evalNode(document, expression);
  }


  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 解析标签
      propertiesElement(root.evalNode("properties"));
      // 解析标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 解析标签
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析标签
      pluginElement(root.evalNode("plugins"));
      // 解析标签
      objectFactoryElement(root.evalNode("objectFactory"));
      // 解析标签
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 解析标签
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      
      // read it after objectFactory and objectWrapperFactory issue #631
      // 解析标签
      environmentsElement(root.evalNode("environments"));
      // 解析标签
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析标签
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

从上面代码中可以得知全局配置文件的解析是从parseConfiguration(parser.evalNode("/configuration"))方法开始的,然后在parseConfiguration这个方法中可以看到它对configuration节点下所有的子节点进行解析。

由于篇幅原原因,本文就先到此结束吧!在结束前,先来做一个简单的总结吧!

 

总结:

涉及的相关类和接口及其功用:

  • SqlSessionFactoryBuilder :用于构建SqlSessionFactory对象
  • XMLConfigBuilder:全局配置文件解析器
  • XPathParser :XPath解析器
  • Configuration:用于存储DataSource、Statement等这些对象(MyBatis所有的配置信息都维持在Configuration对象之中)

基本执行流程:

  • SqlSessionFactoryBuilder#build():构建SqlSessionFactory对象
    • XMLConfigBuilder构造函数:解析全局配置文件解析器
      • XPathParser构造参数:用来使用XPath语法解析XML的解析器
        • XPathParser#createDocument():解析XML全局配置文件,并将其封装为Document对象
      • XMLMapperEntityResolver构造函数:MyBatis DTD 离线实体解析器
      • Configuration构造函数:创建Configuration对象,同时注册一些内置类的别名
      • XMLConfigBuilder#parse():全局配置文件的解析器
        • XPathParser#evalNode(使用xpath语法):XPath解析器,专门用来通过Xpath语法解析XMLXNode节点的
        • XMLConfigBuilder#parseConfiguration(XNode):从全局配置文件根节点开始解析,加载的信息设置到Configuration对象中
    • SqlSessionFactoryBuilder#build:创建SqlSessionFactory接口的默认实现类DefaultSqlSessionFactory,同时传入Configuration对象

 

 

 

你可能感兴趣的:(Mybatis源码解析)