mybatis源码分析(一)初始化

初始化

mybatis初始化主要解析两种配置文件

  1. 一种是mybatis-config.xml,通过解析这个文件来加载用户自定义配置,系统配置,类型别名,java和jdbc类型之间的转换器等
  2. 另外一种是各种Mapper的配置文件,通过解析这个文件来加载每个Mapper方法的sql,参数转换,类型转换等
    首先看下mybatis初始化的代码
// 指定配置文件
Reader reader = Resources
        .getResourceAsReader("org/apache/ibatis/submitted/annotion_many_one_add_columnprefix/SqlMapConfig.xml"));
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession()
UserDao mapper = sqlSession.getMapper(UserDao.class);

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.
    }
  }
}

XMLConfigBuilder.parse

public Configuration parse() {
  // 设置标志位,避免重复解析
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 使用XPathParser来解析配置文件中的confiugration节点
  // 并将这个节点的信息交给parseConfiguration
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    // 当前的root是configuration节点
    // 解析自定义配置
    propertiesElement(root.evalNode("properties"));
    // 解析Mybatis内置的配置项,比如是否开启缓存
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    // 读取vfsImpl,加载对应的虚拟文件系统类,方便读取系统文件
    loadCustomVfs(settings);
    // 读取logImpl,使用指定的日志实现类
    loadCustomLogImpl(settings);
    // 解析类别别名
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析插件
    pluginElement(root.evalNode("plugins"));
    // 下面三个主要就是读取相应的节点,然后创建相应的factory实例,然后设置到configuration中
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // 解析settings节点,将系统配置设置到configuration中
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // 解析environments标签,可以通过为不同的环境使用不同的environment子标签,在environment子标签中可以指定当前环境使用的事务管理企和数据源
    environmentsElement(root.evalNode("environments"));
    // 通过指定dataBaseIdProvider可以适配不同的数据库厂商的产品,在Mapper文件中,可以为同一个id为selectById的sql代码块指定不同的dataBaseId,从而当执行selectById这个方法时,会根据当前使用的数据库产品,执行对应的sql
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 注册java类型和jdbc类型之间的类型转换器
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 这里是最关键的,解析每个mapper
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}
propertiesElement

这个方法主要用来解析自定义配置

private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
  	// 读取properties的所有子标签,分别读取每个标签的name和value属性,放到properties中
    Properties defaults = context.getChildrenAsProperties();
    // 读取properties标签的resource url属性
    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.");
    }
    // 分别根据resource和url来加载指定的配置文件
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    // 加载configuration中的配置
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
    // 将整合好的配置放到parser和configuration中
    parser.setVariables(defaults);
    configuration.setVariables(defaults);
  }
}

从上面的源码可以看出,配置自定义属性一共有两种方法,一种就是简单地在xml中进行配置,比如

<properties>
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</properties>

另外一种就是在properties的resource和url属性中来指定配置文件,其中jdbc.properties中存放了一些和数据库相关的配置

<properties resource="jdbc.properties"/>
settingsAsProperties

这个方法主要用来解析系统配置,比如是否使用缓存等

private Properties settingsAsProperties(XNode context) {
  if (context == null) {
    return new Properties();
  }
  // 遍历settings的所有子节点,读取每个节点的name和value放到properties中
  Properties props = context.getChildrenAsProperties();
  // Check that all settings are known to the configuration class
  // 下面校验这里设置的属性是否是Mybatis内置的
  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;
}
<settings>
     <setting name="defaultExecutorType" value="SIMPLE"/>
     <setting name="useGeneratedKeys" value="true"/>
 </settings>
loadCustomVfs

主要用来读取配置中的vfsImpl,使用指定的虚拟文件系统实现类,来实现读取系统文件的功能

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

主要用来读取配置中的logImpl,使用指定日志实现类

private void loadCustomLogImpl(Properties props) {
  Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
}
public void setLogImpl(Class<? extends Log> logImpl) {
  if (logImpl != null) {
    this.logImpl = logImpl;
    LogFactory.useCustomLogging(this.logImpl);
  }
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
 setImplementation(clazz);
}
private static void setImplementation(Class<? extends Log> implClass) {
  try {
    Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
    Log log = candidate.newInstance(LogFactory.class.getName());
    if (log.isDebugEnabled()) {
      log.debug("Logging initialized using '" + implClass + "' adapter.");
    }
    logConstructor = candidate;
  } catch (Throwable t) {
    throw new LogException("Error setting Log implementation.  Cause: " + t, t);
  }
}

当调用LogFactory.getLog的时候,就会创建我们指定的日志实现类

public static Log getLog(String logger) {
  try {
    return logConstructor.newInstance(logger);
  } catch (Throwable t) {
    throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
  }
}
typeAliasesElement

这个方法是用来解析类型别名的

private void typeAliasesElement(XNode parent) {
 if (parent != null) {
 	// 解析typeAliases标签下的所有子标签
   for (XNode child : parent.getChildren()) {
   	 // 如果是package,那么会将指定包下的所有类
     if ("package".equals(child.getName())) {
       String typeAliasPackage = child.getStringAttribute("name");
       configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
     } else {
       // 其他子标签,typeAliases,只是单独注册一个别名
       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>
  <typeAlias alias="LazyFoo"
    type="org.apache.ibatis.submitted.lazy_deserialize.LazyObjectFoo" />
  <typeAlias alias="LazyBar"
    type="org.apache.ibatis.submitted.lazy_deserialize.LazyObjectBar" />
  <package name="org.apache.ibatis.submitted.autodiscover.aliases" />
</typeAliases>

另外mybatis会内置一些默认的别名,在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);
}

在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);
}
package标签

下面看下mybatis是如何处理package节点的

public void registerAliases(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  // 读取指定包下的所有以.class结尾的并且是superType子类的类文件,将这些类放到集合中
  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);
    }
  }
}

下面看下ResolverUtil.find

public ResolverUtil<T> find(Test test, String packageName) {
  // 将包路径的.使用/进行替换,找到包对应的目录路径
  String path = getPackagePath(packageName);

  try {
    // 显示对应目录下的所有文件
    List<String> children = VFS.getInstance().list(path);
    for (String child : children) {
      // 过滤出文件名后缀是class的,并且使用Test来做进一步的筛选
      if (child.endsWith(".class")) {
        addIfMatching(test, child);
      }
    }
  } catch (IOException ioe) {
    log.error("Could not read package: " + packageName, ioe);
  }

  return this;
}
protected void addIfMatching(Test test, String fqn) {
  try {
    // 将路径名切换成包名
    String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
    ClassLoader loader = getClassLoader();
    if (log.isDebugEnabled()) {
      log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
    }

    Class<?> type = loader.loadClass(externalName);
    if (test.matches(type)) {
      matches.add((Class<T>) type);
    }
  } catch (Throwable t) {
    log.warn("Could not examine class '" + fqn + "'" + " due to a "
        + t.getClass().getName() + " with message: " + t.getMessage());
  }
}
public void registerAlias(Class<?> type) {
  // 获取类名
  String alias = type.getSimpleName();
  // 获取类上的@Alias注解,如果使用了@Alias注解,那么会使用注解的value作为别名
  Alias aliasAnnotation = type.getAnnotation(Alias.class);
  if (aliasAnnotation != null) {
    alias = aliasAnnotation.value();
  }
  registerAlias(alias, type);
}

主要就是根据指定的报名,遍历对应目录下的所有类文件,然后判断类上是否使用了@Alias注解,如果使用了该注解,那么会使用该注解的value作为别名,否则会使用类名作为别名,而且这里会判断该别名是否被使用

typeAlias标签
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 (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
    throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
  }
  typeAliases.put(key, value);
}
pluginElement
private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    // 遍历子节点
    for (XNode child : parent.getChildren()) {
      // 解析节点的Interceptor属性,该属性代表了插件类的包名
      String interceptor = child.getStringAttribute("interceptor");
      // 遍历当前节点的所有子节点,读取每个节点的name和value属性,设置到properties中
      Properties properties = child.getChildrenAsProperties();
      // 生成interceptor实例
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
	  // 将interceptor配置设置到intercetpor实例中,我们自己实现的interceptor可以通过这种设置properties的方法传一些interceptor的参数进来
      interceptorInstance.setProperties(properties);
      // 将interceptor设置到configuration中
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

下面看下Configuration的addInterceptor方法
主要就是调用InterceptorChain类的addInterceptor方法

public void addInterceptor(Interceptor interceptor) {
	  interceptorChain.addInterceptor(interceptor);
	}
environmentElement

在没有结合使用Mybatis和spring之前,如果我们需要为不同的环境使用不同的数据库相关的配置,我们可以配置文件中使用environments标签,其中的每个子标签environment代表一个环境,其中包含该环境的一些数据库配置

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC">
            <property name="" value=""/>
        </transactionManager>
        <dataSource type="UNPOOLED">
            <property name="driver" value="org.hsqldb.jdbcDriver"/>
            <property name="url" value="jdbc:hsqldb:mem:automapping"/>
            <property name="username" value="sa"/>
        </dataSource>
    </environment>
</environments>
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    // 这里会判断是否指定了环境,如果没有指定环境,那么会使用environments标签上的default属性设置的值
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    // 将当前生效的环境配置设置到configuration中
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}
databaseIdProviderElement
<databaseIdProvider type="DB_VENDOR">
  <property name="MySQL" value="mysql" />
   <property name="Oracle" value="oracle" />
</databaseIdProvider>
<mapper namespace="com.yihaomen.mybatis.dao.DatabaseIdProviderMapper">
    <select id="selectTime" resultType="String" databaseId="mysql">
        SELECT NOW() FROM dual
    </select>
    <select id="selectTime" resultType="String" databaseId="oracle">
        SELECT  'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')  FROM dual
    </select>
</mapper>

当使用的是mysql数据库时,当通过Mapper来执行selectTime方法时,会执行SELECT NOW() FROM dual,可以看出,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();
    // resolveClass会将type作为别名找到对应的类,这里会找到type对应的DataBaseIdProvider的实现类,然后实例化
    databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
    databaseIdProvider.setProperties(properties);
  }
  Environment environment = configuration.getEnvironment();
  if (environment != null && databaseIdProvider != null) {
    //  使用DataBaseIdProvider来根据当前环境的数据源来获取对应数据库产品的dataBaseId,然后设置到configuration中
    String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
    configuration.setDatabaseId(databaseId);
  }
}

看下DataBaseIdProvider接口的定义,可以看到最主要的方法就是getDatabaseId,根据数据源类型来返回对应的数据库产品的id

public interface DatabaseIdProvider {

  default void setProperties(Properties p) {
    // NOP
  }

  String getDatabaseId(DataSource dataSource) throws SQLException;
}
typeHandlerElement

这里的主要做的就是解析配置文件中的typeHandlers标签下的所有typeHandler子标签,每个子标签代表java类型和jdbc类型之间的类型转换器
这里的处理和typeAliasElement的处理类似,也提供了两种方法,一种是通过使用package子标签,来对某个包下的类进行批量注册,另外一种就是使用typeHandler节点,来注册一个

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);
        }
      }
    }
  }
}
package标签
public void register(String packageName) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  // 扫描指定的包名下面的类,会将包名转换成路径名,然后遍历每个类,判断是欧服是TypeHandler的子类
  resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
  Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
  for (Class<?> type : handlerSet) {
    //Ignore inner classes and interfaces (including package-info.java) and abstract classes
    if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
      register(type);
    }
  }
}
typeHandler标签

这里就是解析typeHandler节点的javaType,jdbcType,handler属性,然后解析出对应的类,然后注册到map中

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

这里做的就是解析mappers标签的所有子标签,将对应的mapper配置文件或者mapper类注册进来
这里和之前的typeHandler和typeAlias的注册相同,注册形式也分为两种,一种是通过扫描指定包下面的所有类,另外一种就是使用一个标签来注册指定的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);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try(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.");
        }
      }
    }
  }
}
packages标签

扫描指定包下的所有类,将这些类作为Mapper类,并且解析这些Mapper类上的注解,然后注册进来

String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);

Configuration的addMappers会调用MapperRegistry的addMappers

public void addMappers(String packageName) {
  mapperRegistry.addMappers(packageName);
}

然后和typeAlias和typeHandler的处理类似,使用ResolverUtils来扫描指定包下的所有类,这里的过滤条件是只要是类就可以

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) {
  // mapper必须是接口
  if (type.isInterface()) {
    // 判断是否已经注册过
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      // MapperProxyFactory用来生成Mapper接口的实现类,实现类主要做的就是当调用Mapper的方法时,会调用配置的sql
      // MapperProxyFactory的源码分析之前的文章中有
      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.
      // 对Mapper类进行解析
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}
mapper标签

这里主要读取mapper标签上的resource,url和class属性,前面两种属性指定了xml配置文件的路径,class属性则指定了Mapper接口的路径

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);
  try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    mapperParser.parse();
  }
} else if (resource == null && url != null && mapperClass == null) {
  ErrorContext.instance().resource(url);
  try(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.");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值