Mybatis源碼--初始化過程分析

1 概述

所有框架的初始化其實就是讀取配置文件中的數據來生成配置對應的類的對象。針對Mybatis框架的初始化其實就是讀取配置文件和Mapper配置文件來生成Condiguration類的對象。下面我們來看一下具體的初始化過程。

2 利用XML初始化

2.1 應用示例

首先我們來看一個使用Mybatis的簡單示例。示例工程結構如下:

針對上面的示例,關鍵源碼如下:

(1)datasource.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/liutao_dev?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=123

(2)mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="datasource.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- class 級別的指定 -->
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

(3)UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liutao.mybatis.mapper.UserMapper">

  <select id="findUserInfo" resultType="com.liutao.vo.User" parameterType="java.lang.String">
    select name, age,password from user where name = #{username};
  </select>
</mapper>

(4)UserMapper.java

package com.liutao.mybatis.mapper;

import com.liutao.vo.User;
/**
 * 用戶Mapper持久層
 *
 * @author LIUTAO
 * @version 2017/5/23
 * @see
 * @since
 */
public interface UserMapper {

    /**
     * 根據用戶名查詢用戶
     * @param username
     * @return
     */
    User findUserInfo(String username);

}

(5)測試類test.java

import com.liutao.mybatis.mapper.UserMapper;
import com.liutao.vo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author: LIUTAO
 * @Date: Created in 2018/10/15  14:44
 * @Modified By:
 */
public class test {

    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = sqlSessionFactory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = userMapper.findUserInfo("liubei");
        System.out.println("user=" + user);
    }
}

運行測試代碼,我們可以看見輸出結果爲:

user=User{name='liubei', age=12, password='123', id=0}。

上面就是針對簡單示例的源碼的展示,我們這裏要分析mybatis的初始化,也就是分析Configuration對象的創建過程,那麼這個對象又是在哪裏創建的呢?接下來我們來看一下具體的方法調用流程。

2.2 初始化方法調用流程

查看使用demo,首先調用SqlSessionFactoryBuilder的默認構造器生成SqlSessionFactoryBuilder對象。在這裏其實使用了建造者設計模式來生成SqlSessionFactory。接着調用build方法來生成SqlSessionFactory。我們來看一下build方法內部是怎樣實現的。

public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

可以看見最終調用了一個帶有三個參數的build重載方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

在此我們需要注意調用此方法傳入的environment和properties都爲null。

我們來看一下build方法內部做了什麼。
首先生成XMLConfigBuilder對象。
查看XMLConfigBuilder的源碼,我們可以看出它繼承自BaseBuilder。

public class XMLConfigBuilder extends BaseBuilder {
    ... ...

看一下BaseBuilder的源碼

public abstract class BaseBuilder {
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  ... ...

我們發現,BaseBuilder持有Configuration屬性,這個屬性也就是通過配置文件轉換成的對象。BaseBuilder不僅僅有XMLConfigBuilder一個子類,而且擁有以下子類:

這裏調用了XMLConfigBuilder的構造器來生成XMLConfigBuilder。

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

這裏首先要調用XPathParser的構造器來生成一個XPathParser,然後再調用如下構造器。

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

在這裏,我們先來看一下new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())做了些什麼。
XPathParser是用來解析XML文件的,包括檢測XML文件格式。
我們來看以下XPathParser擁有的屬性:

public class XPathParser {
private Document document;// 用來解析xml文件
private boolean validation;//驗證
private EntityResolver entityResolver;//通過key查找dtd文件
private Properties variables;
private XPath xpath;//將元素轉換成爲節點信息


 public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }

在分析這個構造器之前,我們先來看一下傳入的new XMLMapperEntityResolver()是個什麼東西。
XMLMapperEntityResolver的作用其實就是找到對應的dtd文件。我們mybatis的配置文件,不管是屬性配置文件或者是mapper配置文件都會發現有類似下面的內容:

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

其實每個和mybatis相關的配置文件都有內容,也就是有一個PUBLICID和SYSTEMID。
查看XMLConfigBuilder的源碼:

public class XMLMapperEntityResolver implements EntityResolver {

  private static final Map<String, String> doctypeMap = new HashMap<String, String>();

  private static final String IBATIS_CONFIG_PUBLIC = "-//ibatis.apache.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String IBATIS_CONFIG_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-config.dtd".toUpperCase(Locale.ENGLISH);

  private static final String IBATIS_MAPPER_PUBLIC = "-//ibatis.apache.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String IBATIS_MAPPER_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);

  private static final String MYBATIS_CONFIG_PUBLIC = "-//mybatis.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String MYBATIS_CONFIG_SYSTEM = "http://mybatis.org/dtd/mybatis-3-config.dtd".toUpperCase(Locale.ENGLISH);

  private static final String MYBATIS_MAPPER_PUBLIC = "-//mybatis.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
  private static final String MYBATIS_MAPPER_SYSTEM = "http://mybatis.org/dtd/mybatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

可以發現這裏定義了大量的PUBLICID和SYSTEMID。
至於具體的使用,我們後面再談。
現在我們有回過頭來看一下XPathParser構造器內部的實現情況。
首先調用了commonConstructor函數。

private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

這個函數做的其實就是給XPathParser對象進行賦值。
最後調用createDocument函數來生成document。

完成了XMLConfigBuilder對象的初始化之後,會首先調用他的parse函數來生成Configuration。我們來看一下parse函數的具體實現。

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
 

進入evalNode函數。

 public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }

最終調用的是下面的函數:

 public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }  

這個函數的作用其實就是將document中的Configuration節點給解析成XNode對象。
XNode其實就是對XML中的節點進行的封裝。

public class XNode {
  private Node node;
  private String name;
  private String body;
  private Properties attributes;
  private Properties variables;
  private XPathParser xpathParser;
  ... ...

最終parseConfiguration函數利用解析成的XNode對象,來獲取裏面的各種屬性並且設置到Configuration對象上。

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      settingsElement(root.evalNode("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);
    }
 }

最終完成了由XML配置文件的數據到Configuration的轉換。

現在我們回到SqlSessionFactoryBuilder的build方法中。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }

最終調用build(Configuration configuration)方法來生成SqlSessionFactory對象。

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

至此,mybatis的初始化過程分析完成,我們將在後面的文章中分析具體是如何解析xml配置文件和mapper文件的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章