Mybatis源碼--XML配置文件解析(未完待續)

1 概述

前面的文章我們針對Mybatis的初始化的整個過程做了一個分析,我們知道Mybatis的初始化其實就是將配置文件的配置信息最終轉換成Configuration對象的一個過程,現在我們就來看一看這個過程到底做了些什麼。

2 Configuration類詳解

我們知道XML配置文件最終都會解析成Configuration配置類,那麼這裏我們就來看看Configuraion這個類中到底包含什麼東西。(針對XML映射配置文件我們可以參考Mybatis官網:Mybatis

查看mybatis官網,mybatis的xml配置文件主要包含以下內容:

  1. properties 屬性
  2. settings 設置
  3. typeAliases 類型別名
  4. typeHandlers 類型處理器
  5. objectFactory 對象工廠
  6. plugins 插件
  7. environments 環境 
  8. databaseIdProvider 數據庫廠商標識
  9. mappers 映射器

2.1 基本屬性

我們來看一下源碼中定義的Configuration的基本屬性。

/**
 * @author Clinton Begin
 */
public class Configuration {
  
  //環境變量
  protected Environment environment;
  
  //setting設置,可以改變mybatis的運行時行爲
  protected boolean safeRowBoundsEnabled = false;
  protected boolean safeResultHandlerEnabled = true;
  protected boolean mapUnderscoreToCamelCase = false;
  protected boolean aggressiveLazyLoading = true;
  protected boolean multipleResultSetsEnabled = true;
  protected boolean useGeneratedKeys = false;
  protected boolean useColumnLabel = true;
  protected boolean cacheEnabled = true;
  protected boolean callSettersOnNulls = false;

  protected String logPrefix;
  protected Class <? extends Log> logImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
  protected Integer defaultStatementTimeout;
  protected Integer defaultFetchSize;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  
  //properties屬性
  protected Properties variables = new Properties();
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);

  protected boolean lazyLoadingEnabled = false;
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  protected String databaseId;
  /**
   * Configuration factory class.
   * Used to create Configuration for loading deserialized unread properties.
   *
   * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300</a> (google code)
   */
  protected Class<?> configurationFactory;

  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  
  //mapper文件解析後存放於此
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<String>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

  protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
  protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
  protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
  protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

  /*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   */
  protected final Map<String, String> cacheRefMap = new HashMap<String, String>();

 

3 BaseBuilder類詳解

上面我們分析了Configuration類的結構及作用,那麼我們繼續來看看將XML文件中的內容讀取出來設置到Configuration的工具BaseBuilder類。UML類圖如下:

 

3.1 BaseBuilder

直接上源碼,看看裏面包含的東西。

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

BaseBuilder總共包含了三個屬性,這三個屬性的作用分別是:

(1)Configuration:XML配置類。

(2)TypeAliasRegistry:類型別名註冊器,類型別名的訪問入口,可以通過TypeAliasRegistry來獲取和添加類型別名。

(3)TypeHandlerRegister:typeHandler註冊管理類,可以通過這個類來訪問類型處理器。

3.2 XMLConfigBuilder

XMLConfigBuilder用來解析mybatis配置文件,擁有的屬性如下:

public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  private XPathParser parser;
  private String environment;
  private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

 

4 其餘輔助類

4.1 XPathParser

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;//將元素轉換成爲節點信息
    ... ...

4.2 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。
查看XMLMapperEntityResolver的源碼:

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。

4.3 XNode

XNode其實就是對XML中的節點進行的封裝。

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

5 配置文件解析

請看SqlSessionFactoryBuilder中的build方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {

      //通過傳入的配置文件的輸入流來創建XMLConfigBuilder
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

      //通過調用XMLConfigBuilder的parse()方法創建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.
      }
    }
  }

//傳入Configuration來創建SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

從上面的源碼我們可以看出,最終構建Configuration的重擔落在了XMLConfigBuilder的parse()方法上面。

public Configuration parse() {

    //XMLConfigBuilder創建後僅僅能夠被使用一次,如果再次使用則會拋出異常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }

    //設置XMLConfigBuilder已經使用過
    parsed = true;

    //解析配置文件(將在下面詳細分析這一步)
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

這裏首先調用了XPathParser的evalNode方法,來將XPathParser的Document屬性轉換成XNode對象。

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);
}

這裏針對evalNode我們就不深入研究了。

接着,獲得了XNode對象,然後將XNode對象傳入parseConfiguration方法。

private void parseConfiguration(XNode root) {
    try {
      
        //下面的每個方法其實就是對XNode中包含的不同節點內容進行解析,包含了屬性、類別名等節點   
        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);
    }
  }

接下來我們來看一看具體是怎樣去解析這些屬性的。

(1)properties 屬性

將從configuration節點轉換成的XNode節點中獲取到的properties節點的XNode對象傳入propertiesElement函數。

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      /**
       *獲取properties節點的子節點,其實下面就是一些name和value屬性節點,
       *從這些子節點來初始properties。我們知道properties繼承自HashTable。
       */
      Properties defaults = context.getChildrenAsProperties();

      //獲取節點的resource屬性 
      String resource = context.getStringAttribute("resource");

      //獲取節點的url屬性
      String url = context.getStringAttribute("url");

      //節點的resource屬性和url屬性不能夠同時存在
      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.");
      }

      //讀取resource屬性或者url屬性中的properties值,並且存入properties中
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);

      //將properties屬性值存入configuration中
      configuration.setVariables(defaults);
    }
  }

我們來看一下getChildernAsProperties函數做了什麼。

public Properties getChildrenAsProperties() {
    Properties properties = new Properties();

    //循環遍歷子節點
    for (XNode child : getChildren()) {

      //獲取name和value屬性
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {

        //存入properties中
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

其實getChildernAsProperties做的事很簡單就是解析下圖標識的那一坨數據。

(2)typeAliases 類型別名

這裏和properties節點的解析相同,同樣是將從configuration節點下面獲取到的typeAliases子節點作爲參數傳入typeAliasesElement函數。

private void typeAliasesElement(XNode parent) {
    if (parent != null) {

      //遍歷節點下面的子節點
      for (XNode child : parent.getChildren()) {

        //如果子節點的名稱爲package,則包下面的左右類爲類別名
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {

          //分別獲取類別名和類進行註冊
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {

              //這裏需要注意,如果沒有執行類別名則用類的smpleName作爲別名。
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

至此,我們分析了兩個屬性的解析,至於其餘的都和這兩個大同小異,接下來我們重點分析下mapper文件的解析。

5 mapper文件解析

針對mapper文件的解析和上面提到的properties和typeAlias的解析開始都是類似的。獲取到configuration節點下面的mappers子節點的XNode對象,作爲參數傳入mapperElement函數。

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {

      //遍歷mappers節點下面的子節點(即mapper節點)
      for (XNode child : parent.getChildren()) {

        //如果子節點的名稱爲package,將包內的映射器接口實現全部註冊爲映射器
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");

          //通過包名添加映射器
          configuration.addMappers(mapperPackage);
        } else {

          /**
           * 分別獲取子節點的resource、url和class屬性,這裏需要注意的是前兩個屬性對應mapper文
           * 件,而後一個屬性對應class接口,並且一個mappers節點僅僅支持一種映射器類型
           */         
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");

          //resource提供的映射器
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);

            //將resource對應的xml文件轉換成輸入流
            InputStream inputStream = Resources.getResourceAsStream(resource);

            //解析mapper映射文件,前面我們也提到,XMLMpperBuilder是用於解析mapper文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();

          //url提供的映射器
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();

          //class提供的映射器
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);

            //通過接口解析mapper文件
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

針對上面的源碼,我們具體分析下以下幾點:

(1)通過包名添加mapper映射器

將獲取到的包名作爲參數傳入Configuration對象的addMappers方法。

 

 

(2)通過resource解析映射器

(3)class提供的映射器

獲取到class對應的class對象後,直接傳入Configuration的addMapper()方法。

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

實際上再addMapper方法內部調用了MapperRegistry的addMapper方法。

public <T> void addMapper(Class<T> type) {

    //此方法僅僅用於接口
    if (type.isInterface()) {

      //判斷接口對應的Mapper是否已經註冊過,防止重複註冊
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {

        //註冊類和相應的代理工廠,關於MapperProxyFactory的作用我們將在後面的文章中分析
        knownMappers.put(type, new MapperProxyFactory<T>(type));    
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 

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