Mybatis3源码分析(02)-加载Configuration-XMLConfigBuilder

撸了今年阿里、腾讯和美团的面试,我有一个重要发现…….

作者:ashan_li

出处:https://blog.csdn.net/ashan_li/article/category/6047775/1?


Configuration类在Mybatis中的作用

Configuration类保存了所有Mybatis的配置信息。也就是说mybaits-config.xml及UserMapper.xml中所有配置信息都可以在Configruation对象中找到相应的信息。一般情况下Mybatis在运行过程中只会创建一个Configration对象,并且配置信息不能再被修改。如何配置Mybatis可以看这个文档:http://mybatis.org/mybatis-3/zh/configuration.html

Configuration的属性

configuration的属性主要分为两大部分:

  1. 从mybatis-config.xml中读取的配置
  2. 从mapper配置文件或Mapper注解读取的配置

下面简单说一下这两部分的属性与配置的对应关系

从mybatis-config.xml文件中对应的属性

      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 ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
      protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;

      protected Properties variables = new Properties();
      protected ObjectFactory objectFactory = new DefaultObjectFactory();
      protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
      protected MapperRegistry mapperRegistry = new MapperRegistry(this);

      protected boolean lazyLoadingEnabled = false;
      protected ProxyFactory proxyFactory;

      protected final InterceptorChain interceptorChain = new InterceptorChain();
      protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
      protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
      protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

以上属性可以说都是由mybatis-config.xml文件中读取的。

例如文件中的setting配置

    <settings>
      <setting name="cacheEnabled" value="true"/>
      <setting name="lazyLoadingEnabled" value="true"/>
      <setting name="multipleResultSetsEnabled" value="true"/>
      <setting name="useColumnLabel" value="true"/>
      <setting name="useGeneratedKeys" value="false"/>
      <setting name="autoMappingBehavior" value="PARTIAL"/>
      <setting name="defaultExecutorType" value="SIMPLE"/>
      <setting name="defaultStatementTimeout" value="25"/>
      <setting name="defaultFetchSize" value="100"/>
      <setting name="safeRowBoundsEnabled" value="false"/>
      <setting name="mapUnderscoreToCamelCase" value="false"/>
      <setting name="localCacheScope" value="SESSION"/>
      <setting name="jdbcTypeForNull" value="OTHER"/>
      <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>

看配置的内容和Configuration中的属性名称,就大概知道对应关系。相信之后的解析内容了不会太复杂。

从Mapper配置文件中读取的属性

如下属性是从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");

其中最主要的也是相对复杂的有如下两个(Mapper配置文件也主要是配置这两项):

  1. mappedStatements属性,保存了所有Mapper配置文件中的select/update/insert/delete节点信息。属性类型为一个Map,key为sql对应的ID,MappedSatement为一个java对象,保存了一个select/update/insert/delete的节点信息。
  2. resultMaps属性,保存了所有Mapper配置文件中的resultMap节点。

Mapper配置文件也主要是配置select/update/insert/delete/resultMap这几个节点。

Configuration加载过程

针对mybatis-config.xml配置文件和Mapper配置文件,Mybatis也是由两个相对应的类来解析的。

  1. XMLConfigBuilder解析mybatis-config.xml的配置到Configuration中
  2. XMLMapperBuilder解析Mapper配置文件的配置到Configuration中

XMLConfigBuilder.parse()方法

通过SqlSessionFactory获取Configuration的代码

    SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
    System.out.println(sqlSessionFactory.getConfiguration());

再来看SqlSessionFactoryBuilder.build()方法:

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

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

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

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

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

加载具体配置

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

      //从xml配置文件中加载到Configuration对象中
      private void parseConfiguration(XNode root) {
        try {
          //加载properties节点,一般是定义一些变量
          propertiesElement(root.evalNode("properties")); //issue #117 read properties first
          //加载别名
          typeAliasesElement(root.evalNode("typeAliases"));
          //拦截器
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

          settingsElement(root.evalNode("settings"));
          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));

          //加载Mapper的配置文件,最主要的有两个:一个是sql的定义,一个是resultMap
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

加载properties节点

       private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          //先加载property子节点下的属性
          Properties defaults = context.getChildrenAsProperties();
          String resource = context.getStringAttribute("resource");
          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.");
          }
          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);
        }
      }

从这个方法中可以看出配置规则

  1. 可以设置url或resource属性从外部文件中加载一个properties文件
  2. 可以通过property子节点进行配置,如果子节点属性的key与外部文件的key重复的话,子节点的将被覆
  3. 通过编程方式定义的属性最后加载,优先级最高:
        public SqlSessionFactory build(InputStream inputStream, Properties properties)

properties配置示例

    <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

这里加载的主要是给后面的配置作为变量使用!

加载别名


private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { //package的方式,很少用到,略过 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); } } } } }

再看 TypeAliasRegistry源码,发现mybatis已经为定义了很多别名,方便以后的配置

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

还有一个加载通过别名加载class的方法

       public <T> Class<T> resolveAlias(String string) {
        try {
          if (string == null) return null;
          String key = string.toLowerCase(Locale.ENGLISH); // issue #748
          Class<T> value;
          if (TYPE_ALIASES.containsKey(key)) {
            //如果是别名,直接从注册表里返回
            value = (Class<T>) TYPE_ALIASES.get(key);
          } else {
            value = (Class<T>) Resources.classForName(string);
          }
          return value;
        } catch (ClassNotFoundException e) {
          throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
        }
      }

加载Mapper配置文件

     private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                //由XMLMapperBuilder对象解析加载
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
                //由XMLMapperBuilder对象解析加载
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }

一个Mapper的配置文件最终会由XMLMapperBuilder对象解析加载到Configuration对象中。XMLMapperBuilder的解析过程中XMLConfigBuilder解析过程差不多,以后再详细分析!

加载其他配置项

还有一些配置项这里没有讲到,如:插件/拦截器、对象工厂、setting项。这些的加载都比较简单,只要花点心里就可以看明白。在以后分析代码过程中,一定会看到这里配置,到时再进一步研究,不过可以肯定这里配置很多情况下都是使用默认的值。

赞(1) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » Mybatis3源码分析(02)-加载Configuration-XMLConfigBuilder
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏