【MyBatis源码分析】Configuration加载(上篇)

栏目: 数据库 · 发布时间: 7年前

内容简介:【MyBatis源码分析】Configuration加载(上篇)

config.xml解析为org.w3c.dom.Document

本文首先来简单看一下MyBatis中将config.xml解析为org.w3c.dom.Document的流程,代码为上文的这部分:

static {
    try {
        reader = Resources.getResourceAsReader("mybatis/config.xml");
        ssf = new SqlSessionFactoryBuilder().build(reader);
    } 
    catch (IOException e) {
        e.printStackTrace();
    }
}

第3行的代码实现为:

public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    return reader;
}

相当于就是将输入的路径转换为一个字符输入流并返回。

接着继续看静态块第4行的代码,new SqlSessionFactoryBuilder().build(reader),把代码定位到SqlSessionFactoryBuilder类的builder方法,这里使用了多态,直接跟到build方法:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}

解析config.xml的代码在第3行XMLConfigBuilder类的构造方法中,看一下XMLConfigBuilder类的构造方法做了什么:

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

这里的关键是第二行代码的第一个参数XPathParser,看一下实例化XPathParser类的代码:

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

第2行的代码commonConstructor方法没什么好看的,将validation、variables、entityResolver设置到XPathParser类的参数中而已,顺便再实例化一个javax.xml.xpath.XPath出来,XPath用于在XML文档中通过元素和属性进行导航,并对元素和属性进行遍历。

接着看第3行的createDocument方法:

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
}

看一下第5行~第11行的代码设置DocumentBuilderFactory中参数的含义:

  • setValidating表示是否验证xml文件,这个验证是DTD验证
  • setNamespaceAware表示是否支持xml命名空间
  • setIgnoringComments表示是否忽略注释
  • setIgnoringElementContentWhitespace表示是否忽略元素中的空白
  • setCoalescing表示是否将CDATA节点转换为Text节点,并将其附加到相邻(如果有)的Text节点
  • setExpandEntityReferences表示是否扩展实体引用节点

第13行的代码由设置的参数从DocumentBuilderFactory中获取一个DocumentBuilder实例DocumentBuilderImpl,并由第14行的代码设置一个实体解析器,由第15行~第29行的代码设置一个错误处理器。

最后看一下第30行的代码parse方法:

public Document parse(InputSource is) throws SAXException, IOException {
        if (is == null) {
            throw new IllegalArgumentException(
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
                "jaxp-null-input-source", null));
        }
        if (fSchemaValidator != null) {
            if (fSchemaValidationManager != null) {
                fSchemaValidationManager.reset();
                fUnparsedEntityHandler.reset();
            }
            resetSchemaValidator();
        }
        domParser.parse(is);
        Document doc = domParser.getDocument();
        domParser.dropDocumentReferences();
        return doc;
}

看过Spring配置文件解析源码的朋友应该对这一段代码比较熟悉,一样的,使用DocumentBuilder将解析InputSource成org.w3c.dom.Document并将Document存储到XPathParser中。

Document转换为Configuration

前面的代码将config.xml转换为了org.w3c.dom.Document,下一步就是将org.w3c.dom.Document中的内容转换为 Java 对象了,其中最主要的一个对象就是org.apache.ibatis.session.Configuration,还是回到之前的SqlSessionFactoryBuilder的build方法:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}

先看一下第4行的parse方法,parse方法是XMLConfigBuilder中的,之前重点分析了它的属性XPathParser,看一下XMLConfigBuilder的parse方法是如何实现的:

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

这里看一下第6行,可以使用XPathParser的evalNode方法解析标签,后面解析标签会大量用到此方法,此方法将标签解析为XNode,像config.xml(可见上一篇文章的示例)解析完之后的XNode,toString()方法输出的内容是这样的:

<configuration>
<properties resource="properties/db.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="useGeneratedKeys" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Mail" type="org.xrq.mybatis.pojo.Mail"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driveClass}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userName}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/mail.xml"/>
</mappers>
</configuration>

可见xml文件中<configuration>中所有内容都已经被成功解析并放在XNode中了,剩下的只要调用XNode的方法获取自己想要的内容即可。

最后扫一眼parseConfiguration方法,之所以说扫一眼,因为之后要分析里面的一些常用的和重点的内容,这里只是列一下代码而已:

private void parseConfiguration(XNode root) {
    try {
      Properties settings = settingsAsPropertiess(root.evalNode("settings"));
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      loadCustomVfs(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);
    }
}

这里就是逐个解析<configuration>标签下的子标签,并将数据设置到对应的属性中,这里要一个一个看一下。

settings解析

首先看settingsAsPropertiess(root.evalNode(“settings”))这句代码,显而易见这句话获取了<configuration>下的<settings>节点。跟一下代码的实现:

private Properties settingsAsPropertiess(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
}

第5行将节点解析成键值对的形式(Properties是Hashtable的子类),看一下props的toString方法打印的内容:

{useGeneratedKeys=true, lazyLoadingEnabled=true, cacheEnabled=true}

可见settings里面的数据已经被解析成了Properties了。之后还有一步,<settings>标签下的每个<setting>中的name属性不是随便填写的,都是MyBatis支持的配置,因此需要对Properties里面的Key做一个校验,校验的代码就是第7行至第12行的代码,其中有一个name不是MyBatis支持的就会抛出异常,MyBatis初始化整体失败。

至于具体校验的是哪些Key,这就要跟一下第7行的代码了,首先是MetaClass.forClass(Configuration.class, localReflectorFactory),第二个实参是XMLConfigBuilder里面直接new出来的,它的实际类型为DefaultReflectorFactory,看一下forClass方法实现:

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
     return new MetaClass(type, reflectorFactory);
}

看一下new MetaClass做了什么事:

private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
     this.reflectorFactory = reflectorFactory;
     this.reflector = reflectorFactory.findForClass(type);
}

显而易见,继续跟一下第3行的代码DefaultRelectorFactory的findForClass方法:

public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
            // synchronized (type) removed see issue #461
      Reflector cached = reflectorMap.get(type);
      if (cached == null) {
        cached = new Reflector(type);
        reflectorMap.put(type, cached);
      }
      return cached;
    } else {
      return new Reflector(type);
    }
}

不管怎么样都会执行new Reflector(type)这一句代码,看一下此时做了什么事,注意传入的参数是Configuration的class对象:

public Reflector(Class<?> clazz) {
    type = clazz;
    addDefaultConstructor(clazz);
    addGetMethods(clazz);
    addSetMethods(clazz);
    addFields(clazz);
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
}

这么多方法至于具体要看哪个,要注意的是之前XMLConfigBuilder里面对于Key的判断是”!metaConfig.hasSetter(String.valueOf(key))”,代码的意思是判断的是否Key有set方法,那么显而易见这里要继续跟第5行的addSetMethods方法:

private void addSetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
      String name = method.getName();
      if (name.startsWith("set") && name.length() > 3) {
        if (method.getParameterTypes().length == 1) {
          name = PropertyNamer.methodToProperty(name);
          addMethodConflict(conflictingSetters, name, method);
        }
      }
    }
    resolveSetterConflicts(conflictingSetters);
}

到这里应该很明显了,结论就是: <setting>的name属性对应的值,必须在Configuration类有相应的Setter,比如设置了一个属性useGenerateKeys方法,那么必须在Configuration类中有setUseGenerateKeys方法才行

顺便说一下第13行有一个resolveSetterConflicts方法,其作用是: Setter有可能在类中被重载导致有多个,此时取Setter中方法参数只有一个且参数类型与Getter一致的Setter

properties解析

接着看一下propertiesElement(root.evalNode(“properties”))方法,这句读取的是<configuration>下的<properties>节点,代码实现为:

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("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.");
      }
      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);
      configuration.setVariables(defaults);
    }
}

看到第4行~第7行的代码指定了MyBatis的<properties>标签下不能同时指定”resource”属性和”url”属性。

接着第9行~第13行的代码将.properties资源解析为Properties类,最后将Properties类设置到XPathParser和Configuration的variables属性中,variables是一个Propreties变量。

类型别名解析

跳过loadCustomVfs(settings)直接看typeAliasesElement(root.evalNode(“typeAliases”))这行,因为前者我也没看懂干什么用的,后者是用于定义类型的别名的,解析的是<configuration>下的<typeAliases>标签,用过MyBatis的应该很熟悉。看一下源码实现:

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        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) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
}

从源码实现中我们可以知道两点,<typeAliases>标签下可以定义<package>和<typeAlias>两种标签,但是看第4行和第7行的判断,这是一段if…else…,因此可以知道<package>标签和<typeAlias>标签只能定义其中的一种。首先看一下解析<package>标签的代码,第6行的registerAliases方法:

public void registerAliases(String packageName, Class<?> superType){
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for(Class<?> type : typeSet){
      // Ignore inner classes and interfaces (including package-info.java)
      // Skip also inner classes. See issue #6
      if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
        registerAlias(type);
      }
    }
}

第3行根据路径packageName寻找它下面的”.class”文件拿到所有的”.class”文件对应的类的Class,然后遍历所有的Class,做了三层判断

  • 必须不是匿名类
  • 必须不是接口
  • 必须不是成员类

此时此Class对应的类符合条件,会进行注册,通过registerAlias方法进行注册,看一下方法实现:

public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
}

第2行获取Class的simpleName,simpleName指的是移除了包名的名称,比如aa.bb.cc.Mail,getSimpleName()获取的就是Mail。

第3行获取类上面的注解Alias,如果Alias注解中有定义value属性且指定了值,那么第4行~第6行的判断优先取这个值作为Class的别名。

第7行注册别名:

public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    TYPE_ALIASES.put(key, value);
}

其实就做了两步操作:

  1. 将alias全部小写
  2. 将alias以及Class对象放到TYPE_ALIASES中,TYPE_ALIASES是一个HashMap

这样一个流程,就将<package>标签name属性路径下的Class(如果符合要求),全部放到了HashMap中以供使用。

接着看一下<typeAlias>标签的解析,也就是前面说的else部分:

String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
  Class<?> clazz = Resources.classForName(type);
  if (alias == null) {
    typeAliasRegistry.registerAlias(clazz);
  } else {
    typeAliasRegistry.registerAlias(alias, clazz);
  }
} catch (ClassNotFoundException e) {
  throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}

这里先解析<typeAlias>中的alias属性,再解析<typeAlias>中的type属性,当然alias也可以不定义,不定义走的就是第6行的registerAlias方法,定义走的就是第8行的registerAlias方法,这两个重载的registerAlias方法前面也都说过了,就不说了。

默认typeAlias

上面说的是自定义typeAlias,MyBatis本身也默认提供给开发者了一些typeAlias定义,在两处地方。第一处地方在Configuration的构造方法中:

public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    ...
}

第二处地方是在TypeAliasRegistry的构造方法中:

public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
}

对于这些数据,我们可以直接使用registerAlias方法的第一个参数对应的字符串而不需要定义这些typeAlias。


以上所述就是小编给大家介绍的《【MyBatis源码分析】Configuration加载(上篇)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

机器学习

机器学习

周志华 / 清华大学出版社 / 2016-1-1 / 88.00元

机器学习是计算机科学与人工智能的重要分支领域. 本书作为该领域的入门教材,在内容上尽可能涵盖机器学习基础知识的各方面。 为了使尽可能多的读者通过本书对机器学习有所了解, 作者试图尽可能少地使用数学知识. 然而, 少量的概率、统计、代数、优化、逻辑知识似乎不可避免. 因此, 本书更适合大学三年级以上的理工科本科生和研究生, 以及具有类似背景的对机器学 习感兴趣的人士. 为方便读者, 本书附录给出了一......一起来看看 《机器学习》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具