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  logImpl;
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
  protected Set lazyLoadTriggerMethods = new HashSet(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 Issue 300 (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 mappedStatements = new StrictMap("Mapped Statements collection");
  protected final Map caches = new StrictMap("Caches collection");
  protected final Map resultMaps = new StrictMap("Result Maps collection");
  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");

  protected final Collection incompleteStatements = new LinkedList();
  protected final Collection incompleteCacheRefs = new LinkedList();
  protected final Collection incompleteResultMaps = new LinkedList();
  protected final Collection incompleteMethods = new LinkedList();

  /*
   * 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 cacheRefMap = new HashMap();

 

3 BaseBuilder类详解

上面我们分析了Configuration类的结构及作用,那么我们继续来看看将XML文件中的内容读取出来设置到Configuration的工具BaseBuilder类。UML类图如下:Mybatis源码--XML配置文件解析(未完待续)_第1张图片

 

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配置文件都会发现有类似下面的内容:

其实每个和mybatis相关的配置文件都有内容,也就是有一个PUBLICID和SYSTEMID。
查看XMLMapperEntityResolver的源码:

public class XMLMapperEntityResolver implements EntityResolver {

  private static final Map doctypeMap = new HashMap();

  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  void addMapper(Class type) {
    mapperRegistry.addMapper(type);
  }

实际上再addMapper方法内部调用了MapperRegistry的addMapper方法。

public  void addMapper(Class 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(type));    
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 

你可能感兴趣的:(MyBatis,Mybatis源码分析)