Mybatis源码(一)mybatis的主配置文件解析

从今天开始,我们要开始mybatis源码的阅读之旅了,前置知识:JDBCxml配置文件的解析的API。有了这些前置的知识就可以了。今天我们会讲下mybatis的主配置文件的解析过程。废话不多说,直接上代码。mybatis的配置文件如下:

<?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>
  <environments default="development">
    <environment id="development">
      <!-- 数据库连接池 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver" />
        <property name="url"
                  value="jdbc:mysql://localhost:3306/bookstore?characterEncoding=utf-8" />
        <property name="username" value="root" />
        <property name="password" value="root" />
      </dataSource>
    </environment>
  </environments>
  <!-- 加载mapper.xml -->
  <mappers>
    <mapper resource="mapper/DemoMapper.xml"></mapper>
  </mappers>
</configuration>

然后我们在看看主类中的方法

public class Test {
  public static void main(String[] args) throws Exception {
    String resource = "mybatis.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //xml解析完成
    //其实我们mybatis初始化方法 除了XML意外 其实也可以0xml完成
    qlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    Configuration configuration = sqlSessionFactory.getConfiguration();
    //使用者可以随时使用或者销毁缓存
    //默认sqlsession不会自动提交
    //从SqlSession对象打开开始 缓存就已经存在
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //从调用者角度来讲 与数据库打交道的对象 SqlSession
    //通过动态代理 去帮我们执行SQL
    //拿到一个动态代理后的Mapper
    DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("id","1");
    //因为一级缓存 这里不会调用两次SQL
    System.out.println(mapper.selectMyAll());
    //如果有二级缓存 这里就不会调用两次SQL
    //当调用 sqlSession.close() 或者说刷新缓存方法, 或者说配置了定时清空缓存方法  都会销毁缓存
    sqlSession.close();
  }
}

今天我们主要看SqlSessionFactoryBuilder().build(inputStream);方法的调用链,具体的代码如下;

public class SqlSessionFactoryBuilder {
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //解析config.xml(mybatis解析xml是用的  java dom)     dom4j sax...
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //parse(): 解析config.xml里面的节点
      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.
      }
    }
  }
}

上面的代码走来创建了XMLConfigBuilder对象,让我们打开对应代码一探究竟吧,具体的代码如下:

public class XMLConfigBuilder extends BaseBuilder {
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    //底层使用java原生的xml解析类 environment = null props = null
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //注册类常用的类到typeAliasRegistry environment = null props = null
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
}

public class 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);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }
}

上面的代码就是创建XMLConfigBuilder对象,同时加XPathParser对象赋值给XMLConfigBuilder的成员变量parser,同时往typeAliasRegistry添加了一些常用类。这个类Configuration初始化的同时,往typeAliasRegistry添加了一些的常用的数据类型。typeAliasRegistry主要是一个Map,键是String,值是Class。到此整个解析类创建完成了。还有配置类已经创建完成。下面我们来看具体的解析的方法parser.parse(),具体的代码如下:

public class XMLConfigBuilder extends BaseBuilder {
  public Configuration parse() {
    //是否已经解析过了
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //先将对应的配置文件的解析状态设置为true
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
}

上看的代码直接解析节点configuration,这个节点也是mybatis的根节点,同时将这个节点返回的对应传入到parseConfiguration方法中,然后执行parseConfiguration方法,具体的对象如下:

public class XMLConfigBuilder extends BaseBuilder {
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //设置配置参数,可以从classpath中获取(resource),可以从网路上获取(url)
      propertiesElement(root.evalNode("properties"));
      //解析mybatis中Configuration一些定义好的参数设置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //设置vfs
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 所谓别名 其实就是吧你指定的别名对应的class存储在一个Map当中
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      //objectFactory自定义实例化对象的行为  比如说返回User 对象
      objectFactoryElement(root.evalNode("objectFactory"));
      //MateObject   方便反射操作实体类的对象
      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);
    }
  }
}

上面的代码走来解析是properties节点,具体的节点的格式和解析的代码如下:

<properties resource="" url="">
  <property name="" value=""/>
</properties>
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    //获取字节点的属性(遍历每个字节的属性,设置到properties中去)
    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) {
      //从classpath加载
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      //从网络上加载
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    //获取所有的配置项
    Properties vars = configuration.getVariables();
    //不为空,直接设置到defaults
    if (vars != null) {
      defaults.putAll(vars);
    }
    //将这些的配置项设置给parser和configuration
    parser.setVariables(defaults);
    configuration.setVariables(defaults);
  }
}

上面的方法就是将从properties节点中获取到的配置项设置到parserconfiguration类中去。加载的方式有两种,一种是从classpath中加载(resource),一种是从网络中加载(url)。主要是一些运行的配置文件,可以用来配置数据库的连接的参数。我们继续看后面的方法settingsAsProperties(root.evalNode("settings"));,解析的是setting的节点,具体的节点格式和解析的代码如下:

<settings>
  <setting name="" value=""/>
</settings>
private Properties settingsAsProperties(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()) {
    //判断是否有set方法,如果没有直接报错
    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;
}

获取settings标签中的值,并添加到Properties对象中去,如果对应的数据没有set方法,就会抛出异常。同时这些设置参数都是Configuration中定义好的。然后返回会执行loadCustomVfs(settings);,具体的代码如下:

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
  String value = props.getProperty("vfsImpl");
  if (value != null) {
    String[] clazzes = value.split(",");
    for (String clazz : clazzes) {
      if (!clazz.isEmpty()) {
        @SuppressWarnings("unchecked")
        Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
        configuration.setVfsImpl(vfsImpl);
      }
    }
  }
}

上面的代码就是设置vfs的实现,如果没有配置,就是默认的情况。我们继续看loadCustomLogImpl(settings);方法,这个方法是设置日志的实现类,具体的代码如下:

private void loadCustomLogImpl(Properties props) {
  Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
}

上面的代码就是创建好对应的日志实现类,然后设置到configuration中去。接下来会执行typeAliasesElement(root.evalNode("typeAliases"));方法,解析对应的别名,解析的格式和具体的代码如下:

<typeAliases>
  <typeAlias type="" alias=""/>
</typeAliases>
<typeAliases>
  <package name=""/>
</typeAliases>

上面的两种配置方式只能用一种,下面的代码也证明了此事

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()等于typeAliasRegistry可能是忘了吧
        //遍历此包名下所有的类,alias默认是class的SimpleName
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        try {
          Class<?> clazz = Resources.classForName(type);
          if (alias == null) {
            //alias默认是class的SimpleName
            typeAliasRegistry.registerAlias(clazz);
          } else {
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}

上面的注册别名的方式有两种,一种是配置package,遍历package下的所有的类,全部添加到typeAliasRegistryMap中去,alias是类的SimpleNametypeclass,后面反射创建这些类。第二种是配置typeAlias,指定type属性是Class,指定alias是别名,如果没有指示,则用classSimpleName。然后执行pluginElement(root.evalNode("plugins"));,这个是创建插件,后面我们再说,我们直接看objectFactoryElement(root.evalNode("objectFactory"));方法,具体的解析格式和解析的代码如下:

<objectFactory type="">
  <property name="" value=""/>
</objectFactory>
private void objectFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    //循环读取字节点
    Properties properties = context.getChildrenAsProperties();
    ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(properties);
    configuration.setObjectFactory(factory);
  }
}

上面的添加objectFactory的类,必须要继承DefaultObjectFactory类,然后走来获取type属性,然后再循环读取对应的子节点,然后先从typeAliasRegistryMap中获取这个type,如果没有就直接创建,然后获取默认的构造参数并调用,然后将上面获取到properties设置进去,最后再加这个对象设置到configuration对象中去。然后调用objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));方法,具体的解析的格式和代码如下:

<objectWrapperFactory type=""/>
  •  
private void objectWrapperFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    ObjectWrapperFactory factory = (ObjectWrapperFactory)
      resolveClass(type).getDeclaredConstructor().newInstance();
    configuration.setObjectWrapperFactory(factory);
  }
}

上面的代码和前面的解析objectFactory节点的很像,这儿就不做细说了。接下来我们看reflectorFactoryElement(root.evalNode("reflectorFactory"));这个方法,具体的解析内容和解析的代码如下:

<reflectorFactory type=""/>
  •  
private void reflectorFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    configuration.setReflectorFactory(factory);
  }
}

你会发现又是同样的代码,这儿也不做细说了,我们直接看后面的方法settingsElement(settings);,具体的代码如下:

private void settingsElement(Properties props) {
  configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
  configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
  configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
  configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
  configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
  configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
  configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
  configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
  configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
  configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
  configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
  configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
  configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
  configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
  configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
  configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
  configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
  configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
  configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
  configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
  configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
  configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
  configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
  configuration.setLogPrefix(props.getProperty("logPrefix"));
  configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

设置上面在settings节点中配置的配置项,如果没有配置,就用默认的配置项。我们继续看后面的方法environmentsElement(root.evalNode("environments"));,具体解析的内容和解析的方法如下:

<environments default="development">
  <environment id="development">
    <!-- 使用jdbc事务管理 -->
    <transactionManager type="JDBC" />
    <!-- 数据库连接池 -->
    <dataSource type="POOLED">
      <property name="driver" value="com.mysql.cj.jdbc.Driver" />
      <property name="url"
                value="jdbc:mysql://localhost:3306/bookstore?characterEncoding=utf-8" />
      <property name="username" value="root" />
      <property name="password" value="5201314ysX@" />
    </dataSource>
  </environment>
</environments>
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      //判断是否和父节点中default的属性一致,一致再继续执行
      if (isSpecifiedEnvironment(id)) {
        //解析transactionManager节点并创建TransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //解析dataSource节点并创建DataSourceFactory
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        //获取对应的数据源
        DataSource dataSource = dsFactory.getDataSource();
        //创建Environment
        Environment.Builder environmentBuilder = new Environment.Builder(id)
          .transactionFactory(txFactory)
          .dataSource(dataSource);
        //将Environment设置到configuration中去
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    //从typeAliases中取Jdbc的键,由于我们前面注册了,肯定能取到JDBC->JdbcTransactionFactory.class
    TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    //从typeAliases中取POOLED的键,由于我们前面注册了,肯定能取到POOLED->PooledDataSourceFactory.class
    DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

上面的代码解析transactionManager节点和dataSource节点,这儿可以指定第三方的transactionManagerdataSource,只需要配置对应的别名即可,所以懂了原理是真的简单。我们继续阅读databaseIdProviderElement(root.evalNode("databaseIdProvider"));方法,这个方法解析的格式和解析的代码如下:

<databaseIdProvider type="">
  <property name="" value=""/>
</databaseIdProvider>
private void databaseIdProviderElement(XNode context) throws Exception {
  DatabaseIdProvider databaseIdProvider = null;
  if (context != null) {
    String type = context.getStringAttribute("type");
    // awful patch to keep backward compatibility
    if ("VENDOR".equals(type)) {
      type = "DB_VENDOR";
    }
    Properties properties = context.getChildrenAsProperties();
    //这儿获取的DB_VENDOR->VendorDatabaseIdProvider.class
    databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
    databaseIdProvider.setProperties(properties);
  }
  Environment environment = configuration.getEnvironment();
  if (environment != null && databaseIdProvider != null) {
    String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
    configuration.setDatabaseId(databaseId);
  }
}

上面的代码就是配置多数据源,和前面的代码又是如此的相似,从typeAliasRegistry中获取对应键的值,并创建对应的类,这儿的类是VendorDatabaseIdProvider.class,这儿配置多数据源的方法,就是指定对应的数据源的配置的ID。接下来我们继续看typeHandlerElement(root.evalNode("typeHandlers"));方法,解析的格式和解析的代码如下:

<typeHandlers>
  <package name=""/>
</typeHandlers>
<typeHandlers>
  <typeHandler handler="" javaType="" jdbcType=""/>
</typeHandlers>
private void typeHandlerElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
        String handlerTypeName = child.getStringAttribute("handler");
        Class<?> javaTypeClass = resolveClass(javaTypeName);
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

上面的代码是注册对应的类型的处理器,mybatis已经为我们注册了一部分类型处理器。读取的是MappedJdbcTypes注解中jdbcType,如果没有指定,就注册为null。我们继续看方法mapperElement(root.evalNode("mappers"));主要的解析格式和解析代码的如下:

<mappers>
  <package name=""/>
</mappers>

<mappers>
  <mapper resource="mapper/DemoMapper.xml"></mapper>
</mappers>
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    //遍历解析mappers节点
    for (XNode child : parent.getChildren()) {
      //首先解析package节点  到底是解析注解还是解析xml
      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);
          //解析resource.xml
          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 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.");
        }
      }
    }
  }
}

我们走来先看package的解析过程,对应代码如下:

public class Configuration {
	public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
	}
}
public class MapperRegistry {
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    //找到这个包名下面的所有的类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }
  
  public <T> void addMapper(Class<T> type) {
    //判断是 接口
    if (type.isInterface()) {
      //如果已经注册直接抛出异常
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        //type.getName().replace('.', '/') + ".java (best guess)";
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}

public class MapperAnnotationBuilder {
  public void parse() {
      String resource = type.toString();
      //如果之前解析了xml  这里就不会在解析了
      if (!configuration.isResourceLoaded(resource)) {
        loadXmlResource();
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        Method[] methods = type.getMethods();
        for (Method method : methods) {
          try {
            // issue #237
            if (!method.isBridge()) {
              parseStatement(method);
            }
          } catch (IncompleteElementException e) {
            configuration.addIncompleteMethod(new MethodResolver(this, method));
          }
        }
      }
      parsePendingMethods();
    }
}

上面的代码,走来执行 loadXmlResource();方法,具体的代码如下:

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      //先从当前这个类所在的地方找这个xml
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {//找不到
        // Search XML mapper that is not in the module but in the classpath.
        try {
          //直接搜索classpath路径下的文件,并且包名是一样
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        //调用对应的解析方法
        xmlParser.parse();
      }
    }
  }

上面的XML文件的找取的方式有两种,先从当前这个类所在的地方找这个xml,如果没有找到就去classpath中去找,如果不为空,就直接创建XMLMapperBuilder对象,然后调用对应parse方法,具体的代码如下:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //绑定Namespace里面的Class对象
    bindMapperForNamespace();
  }
  
  //重新解析之前解析不了的节点
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
  • 走来判断这个resource这个文件是否加载过,如果没有加载过,会执行后面的代码,这个时候会执行configurationElement(parser.evalNode("/mapper")方法,具体的代码如下:
// 解析mapper文件里面的节点
// 拿到里面配置的配置项 最终封装成一个MapperedStatemanet
private void configurationElement(XNode context) {
  try {
    //获取namespace的值,如果为空直接抛出异常
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    //将namespace设置到currentNamespace变量中去
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}
  • 首先我们先看解析mapper节点下面的cache-ref的节点,具体的解析的方法是cacheRefElement(context.evalNode("cache-ref"));具体的解析格式和解析的代码如下:
<cache-ref namespace=""/>
  •  
private void cacheRefElement(XNode context) {
  if (context != null) {
    //将对应的配置项加入到configuration配置中去
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), 
                              context.getStringAttribute("namespace"));
    //创建对应的对象
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, 
                                                             context.getStringAttribute("namespace"));
    try {
      //使用对应的缓存,缓存后面后细说
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}

上面的代码就是读取cache-ref节点的内容,注册到configuration中去,至于缓存这块后面会细说。这儿不展开详细说。我们继续看cacheElement(context.evalNode("cache"));方法,具体的解析格式和解析的代码如下:

<cache blocking="" eviction="" flushInterval="" readOnly="" size="" type="">
  <property name="" value=""/>
</cache>
private void cacheElement(XNode context) {
  if (context != null) {
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    String eviction = context.getStringAttribute("eviction", "LRU");
    //LruCache.class
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    Long flushInterval = context.getLongAttribute("flushInterval");
    Integer size = context.getIntAttribute("size");
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    Properties props = context.getChildrenAsProperties();
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

走来先将type属性中读到的值,注册到typeAliasRegistry中去。同时也会读取eviction的值,将其注册到typeAliasRegistry中去,最后获取所有的配置,将其设置到configuration中的cache属性中去。接下来我们继续看parameterMapElement(context.evalNodes("/mapper/parameterMap"));方法,具体的解析格式和解析的代码如下:

<parameterMap id="" type="">
  <parameter property="" jdbcType="" javaType="" typeHandler="" mode="" resultMap="" scale=""/>
</parameterMap>
private void parameterMapElement(List<XNode> list) {
  for (XNode parameterMapNode : list) {
    String id = parameterMapNode.getStringAttribute("id");
    String type = parameterMapNode.getStringAttribute("type");
    Class<?> parameterClass = resolveClass(type);
    List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
    List<ParameterMapping> parameterMappings = new ArrayList<>();
    for (XNode parameterNode : parameterNodes) {
      String property = parameterNode.getStringAttribute("property");
      String javaType = parameterNode.getStringAttribute("javaType");
      String jdbcType = parameterNode.getStringAttribute("jdbcType");
      String resultMap = parameterNode.getStringAttribute("resultMap");
      String mode = parameterNode.getStringAttribute("mode");
      String typeHandler = parameterNode.getStringAttribute("typeHandler");
      Integer numericScale = parameterNode.getIntAttribute("numericScale");
      ParameterMode modeEnum = resolveParameterMode(mode);
      Class<?> javaTypeClass = resolveClass(javaType);
      //String  --> varchar
      JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
      Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
      ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, 
                                                                                 javaTypeClass, jdbcTypeEnum, 
                                                                                 resultMap, modeEnum, 
                                                                                 typeHandlerClass, 
                                                                                 numericScale);
      parameterMappings.add(parameterMapping);
    }
    builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
  }
}

读取parameterMap节点,同时读取字节点parameter,最终将这些东西赋值给configurationparameterMaps属性。接下来我们继续看resultMapElements(context.evalNodes("/mapper/resultMap"));方法,具体的解析的内容和解析的代码如下:

<resultMap id="" type="">
  <association property="" resultMap="" typeHandler="" javaType="" jdbcType="" autoMapping="" column="" columnPrefix="" fetchType="" foreignColumn="" notNullColumn="" resultSet="" select=""/>
  <collection property="" select="" resultSet="" notNullColumn="" foreignColumn="" fetchType="" columnPrefix="" column="" autoMapping="" jdbcType="" javaType="" typeHandler="" resultMap="" ofType=""/>
  <constructor>
    <arg resultMap="" typeHandler="" javaType="" jdbcType="" column="" columnPrefix="" select="" name=""/>
    <idArg name="" select="" columnPrefix="" column="" jdbcType="" javaType="" typeHandler="" resultMap=""/>
  </constructor>
  <discriminator javaType="" typeHandler="" jdbcType="" column="">
    <case value="" resultMap="" resultType="">
    </case>
  </discriminator>
  <id column="" jdbcType="" typeHandler="" javaType="" property=""/>
  <result property="" javaType="" typeHandler="" jdbcType="" column=""/>
</resultMap>
private void resultMapElements(List<XNode> list) throws Exception {
  for (XNode resultMapNode : list) {
    try {
      resultMapElement(resultMapNode);
    } catch (IncompleteElementException e) {
      // ignore, it will be retried
    }
  }
}

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
  return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?
                                   > enclosingType) throws Exception {
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  String type = resultMapNode.getStringAttribute("type",                                           resultMapNode.getStringAttribute("ofType",resultMapNode.getStringAttribute("resultType",                                                                                                    resultMapNode.getStringAttribute("javaType"))));
  Class<?> typeClass = resolveClass(type);
  if (typeClass == null) {
    typeClass = inheritEnclosingType(resultMapNode, enclosingType);
  }
  Discriminator discriminator = null;
  List<ResultMapping> resultMappings = new ArrayList<>();
  resultMappings.addAll(additionalResultMappings);
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    if ("constructor".equals(resultChild.getName())) {
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  String id = resultMapNode.getStringAttribute("id",
                                               resultMapNode.getValueBasedIdentifier());
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    return resultMapResolver.resolve();
  } catch (IncompleteElementException e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

上面的代码就是解析一连串的resultMap标签下的值,最终设置到configurationresultMaps中去,我们再来看看sqlElement(context.evalNodes("/mapper/sql"));方法,具体的解析的格式和代码如下:

<sql id="" databaseId="" lang="">
</sql>
private void sqlElement(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    sqlElement(list, configuration.getDatabaseId());
  }
  sqlElement(list, null);
}

private void sqlElement(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    String databaseId = context.getStringAttribute("databaseId");
    String id = context.getStringAttribute("id");
    id = builderAssistant.applyCurrentNamespace(id, false);
    if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      sqlFragments.put(id, context);
    }
  }
}

走来先判断databaseId这个属性是不是为空,最终调用的方法都是一样的只不过传的参数不一样,如果databaseId的值不为空,则传databaseId的值,如果为空直接传null,上面的代码最终将sql标签中值存入到sqlFragments变量中去了。接下来我们继续看buildStatementFromContext(context.evalNodes("select|insert|update|delete"));方法,这个就是用来解析select|insert|update|delete对应的标签,具体的解析格式不展示了,我们只需要看下具体的解析代码

private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //解析xml节点
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      //xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
  //select/delete/update/insert
  String nodeName = context.getNode().getNodeName();
  //转成大写
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  //判断是不是查询
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  //是否刷新缓存 默认值:增删改刷新 查询不刷新
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  //是否使用二级缓存 默认值:查询使用 增删改不使用
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  //是否需要处理嵌套查询结果 group by
  // 三组数据 分成一个嵌套的查询结果
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  //替换Includes标签为对应的sql标签里面的值
  includeParser.applyIncludes(context.getNode());
  //获取parameterType的值
  String parameterType = context.getStringAttribute("parameterType");
  //从typeAliasRegistry中取出这个值
  Class<?> parameterTypeClass = resolveClass(parameterType);
  //解析配置的自定义脚本语言驱动 mybatis plus
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);
  // Parse selectKey after includes and remove them.
  // 解析selectKey
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  //设置主键自增规则
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                                               configuration.isUseGeneratedKeys() && 
                                               SqlCommandType.INSERT.equals(sqlCommandType))
      ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }
  //解析Sql  根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", 
StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                                      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, 
                                      resultTypeClass,
                                      resultSetTypeEnum, flushCache, useCache, resultOrdered,
                                      keyGenerator, keyProperty, keyColumn, databaseId, langDriver,
                                      resultSets);
}

走来先判断mappedStatements又没有这个注册好的ID,如果有的话,就直接返回,如果没有的话,就直接执行下面的解析。然后通过context.getNode().getNodeName();获取这个节点的名称(select,delete,insert,update),然后转成大写,然后判断是不是查询,如果不是查询后面的就不用刷新缓存,同时也不用二级缓存。然后替换其中的include标签中的内容。然后获取获取parameterType的值,从typeAliasRegistry中取出这个值取出这个值。然后解析selectKey的标签。然后设置主键自增规则。然后解析Sql 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析,我们主要看下langDriver.createSqlSource(configuration, context, parameterTypeClass);代码,具体的代码如下:

public class XMLLanguageDriver implements LanguageDriver {
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }
}

public class XMLScriptBuilder extends BaseBuilder {
  public SqlSource parseScriptNode() {
      //# $
      MixedSqlNode rootSqlNode = parseDynamicTags(context);
      SqlSource sqlSource;
      if (isDynamic) {
        //不解析
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
      } else {
        //用占位符方式来解析
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
      }
      return sqlSource;
    }
}

这儿的代码就不跟进去去了,这儿的就是解析mapperselect|delete|insert|update等标签,如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析。最后标签中内容解析完成了,会继续解析标签中其他的属性,然后根据这些属性创建MappedStatement对象,最后添加到configuration对象中去。最后整个解析的方法就说完了,我们回到原来的地方,具体的代码如下:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //绑定Namespace里面的Class对象
    bindMapperForNamespace();
  }
  //重新解析之前解析不了的节点
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

将解析好的节点添加到LoadedResourceset集合中去,表示已经解析完成了。这个时候绑定Namespace里面的Class对象,由于是从上往下解析,所以可能前面用到的东西在后面才声明了,所以在这又重新解析了一篇。我们再回到原来执行代码的地方,具体的代码如下:

public void parse() {
  String resource = type.toString();
  //如果之前解析了xml  这里就不会在解析了
  if (!configuration.isResourceLoaded(resource)) {
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    parseCache();
    parseCacheRef();
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        // issue #237
        if (!method.isBridge()) {
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

你会发现这儿又执行了一次configuration.addLoadedResource(resource);方法,就很奇怪,是源码的问题?不清楚。然后设置当前的工作路径,解析缓存,这个后面会详细说。这个方法执行完了,我们再回到最原始的方法。

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    //遍历解析mappers节点
    for (XNode child : parent.getChildren()) {
      //首先解析package节点  到底是解析注解还是解析xml
      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);
          //解析resource.xml
          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 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.");
        }
      }
    }
  }
}

package的解析的流程已经讲完了。我们再来看看mapperParser.parse();方法,打开的代码如下:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //绑定Namespace里面的Class对象
    bindMapperForNamespace();
  }
  //重新解析之前解析不了的节点
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

这个时候,你会发现代码如此的相似,后续的流程这儿就不细说了,前面已经说过了。至此整个过程就执行完了,最后将configuration对象赋值给DefaultSqlSessionFactory对象,至此整个解析的过程就讲完。还有一些细节性的问题,笔者在后面会详细说明。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值