Mybatis源码解析(二) -- xml配置文件解析

517 阅读14分钟

Mybatis.xml

在SqlSessionFactoryBuilder中有一个build方法入口,用来解析配置文件。 这里传入配置文件的输入流,也能使用不同的解析方式,传入对应的环境,或者具体的参数。

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

在这之后,它会创建一个XML解析器来解析xml文件。调用parse()方法。parse()会返回一个Configuration对象.mybatis用的解析器是java自带的dom解析

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //创建解析器
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //parse()用来解析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.
      }
    }
  }

最后将这个SqlSessionFactory返回,我们就能用这个Factory来创建Session了。

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

我们开始分析具体的解析流程,XMLConfigBuilder调用自身的parse方法来进行xml的解析。 在无参的parse()方法中,它需要先检查是否已经解析过了,parsed默认为false,是SqlSessionFactoryBuilder中的成员变量,当解析完后将其置为true。

当然你也可以创建新的SqlSessionFactoryBuilder来再次解析xml,这么做是没有必要的。

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

在判断完之后,开始解析节点,我们传入根节点configuration来进行解析。mybatis配置文件中的节点顺序是不能进行颠倒的,从下面的代码也能看出节点的解析是按照顺序。最先解析的是properties,最末是mappers。

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      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>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test1"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</properties>

方法二:

<!--这里也能化成url-->
<properties resource="db.properties"></properties> 

db.properties文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/sql05
username=root
password=123

解析源码

//这里传入properties节点
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //解析properties节点下面的子节点,并记录property节点
      Properties defaults = context.getChildrenAsProperties();
      //获取使用resource的配置方式
      String resource = context.getStringAttribute("resource");
      //获取使用url的配置方式
      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
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
        //解析url
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //获取配置文件的其他成员变量,也就是configuration类中的Properties对象。
      //这时候还没有解析其他的节点,所以Properties是空的。将解析好的Properties节点数据存入到configuration类中的Properties对象中。
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

这里还需要注意的是,由于Properties的父类是hashtable,所以当执行putAll方法时,会覆盖已存在的同名Node节点信息。 由于是先解析的property子节点,后解析resource或者url节点,所以property节点可能会被覆盖。

如果property中有需要动态替换的值,会在解析resource或者url的时候再进行替换。

settings节点

Mybatis一些配置。

使用方式

    <settings>
        <!-- 全局的映射器开启或禁用缓存 (默认true) -->
        <setting name="cacheEnabled" value="true" />
        <!-- 全局开启或管理延迟加载。当开启时,所有关联对象都会延迟加载 (默认false) -->
        <setting name="lazyLoadingEnabled" value="true" />
        <!--还有很多,不一一列举-->
        <!-- ... -->
    </settings>

解析源码

  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    //解析seethings下面的子节点
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    
    //这里创建了一个MetaClass
    //这是mybatis的工具类用来检查,这里用来configuration中是否有对应的set方法。
    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中的参数都放到一个临时容器props中,还没有写入到configuration对象。

  private void parseConfiguration(XNode root) {
    try {
      //...
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //这里也是settings中的两个参数,单独进行解析
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
	  //...
      //这里才将参数设置到Configuration。因为上文还要解析其他的一些配置。
      settingsElement(settings);
      //...
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

最后会将这些配置一个个从临时容器中取出,放到configuration中

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

typeAliases

类型别名可为 Java 类型设置一个缩写名字。 它仅用于XML配置,意在降低冗余的全限定类名书写。

使用方式

方式一:使用包名,mybatis会自动扫名包,并给它一个缩写的别名。如com.entity.User会被自动赋予别名User

  <typeAliases>
    <package name="com.entity"/>
  </typeAliases>

方式二:直接指定别名,这种方式可以手动的将类指定为想要的任意名字,这里我将其指定为MyUser。

<typeAliases>
  <typeAlias alias="MyUser" type="com.entity.User"/>
</typeAliases>

当然也能是方式一和方式二两者相结合的方式。

解析源码

  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      //这里便利子节点,判断是方式一的package类型还是方式二的类型。
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          //如果是包的类型,需要获取包路径,并扫描,最后添加到configuration的别名库中。
          String typeAliasPackage = child.getStringAttribute("name");
          //TypeAliasRegistry是别名的注册中心,类似的还有TypeHandlerRegistry类型处理器注册中心等
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
        //如果是自行指定的别名,那么直接添加到别名库中,当然如果不指定别名,那么使用的就是全路径类名。
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          // com.luban.DemoMapper    alias = DemoMapper  type :com.luban.DemoMapper
          try {
          //这里使用反射去获取类,如果获取不到会抛出ClassNotFoundException异常
            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);
          }
        }
      }
    }
  }

在注册别名时,会将其转化为小写。如果发现同名的情况,会直接抛出异常。

  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    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);
  }

包扫描代码,能扫描包里面的所有子包以及其下的所有java文件

  //TypeAliasRegistry.registerAliases
  public void registerAliases(String packageName) {
    registerAliases(packageName, Object.class);
  }
  //TypeAliasRegistry.registerAliases
  public void registerAliases(String packageName, Class<?> superType) {
    //ResolverUtil用来解析并校验包下面的类
    //ResolverUtil.IsA是一个内部类,用来校验当前类是否是superType类或者superType的子类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    //resolverUtil.find用来查找包下面的,superType的所有子类.是这里传入的是Object,那也就是查找所有的java类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    for (Class<?> type : typeSet) {
      // 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) {
    //获取包的路径 如com/entity
    String path = getPackagePath(packageName);
    try {
    //这个方法获取包下面的所有文件的路径 + 名称
      List<String> children = VFS.getInstance().list(path);
      for (String child : children) {
      	//判断是否是Java的class文件
        if (child.endsWith(".class")) {
        //判断是否是待检查父类(Object)的子类,如果是,则添加到set集合中
          addIfMatching(test, child);
        }
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }
    return this;
  }
  //VSF.list
  public List<String> list(String path) throws IOException {
    List<String> names = new ArrayList<>();
    for (URL url : getResources(path)) {
      names.addAll(list(url, path));
    }
    return names;
  }

获取当前线程的classLoader来解析路径,最后使用java的java.lang.ClassLoader.getResources来解析路径

  protected static List<URL> getResources(String path) throws IOException {
    return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
  }

除了我们手动配置的别名之外,别名注册器TypeAliasRegistry还会在构造方法中初始化一些常用的别名。这里就不作展示。

plugin

使用方式

<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

解析源码

其中property参数能在setProperties(Properties properties)中获取到。 这部分代码就是初始化一个拦截器,并添加到拦截器链中。不熟悉拦截器的朋友可以返回章节一查看。

  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        //调用InterceptorChain.addInterceptor
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

objectFactory

Mybatis有一个对象工厂DefaultObjectFactory来创建结果对象的实例。如果我们需要自定义的创建对象,就可以声明一个对象工厂。这个配置一般较少使用。

使用方式

<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

这里就是初始化工厂实例,并将这个工厂实例塞到Configuration中,替换原来默认的工厂。

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

objectWrapperFactory

使用方式

提供一个包装器包装器工厂,来根据具体的java对象,返回包装器。默认DefaultObjectWrapperFactory中的方法是空的。自定义的包装器需要实现ObjectWrapper接口。

<objectWrapperFactory type="com.wrapperfactory.MapWrapperFactory"/>

解析源码

//包装器工厂。
public interface ObjectWrapperFactory {
 //判断该对象是否有包装器
  boolean hasWrapperFor(Object object);
  //返回对象的包装器
  ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
}

代码与对象工厂类似,简单的实例化对象。

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

reflectorFactory

用来缓存Reflector的工厂,Reflector类用来计入java类的信息,如get,set,方法信息等。

使用方式

<reflectorFactory type="com.reflectorFactory.MyReflectorFactory"/>

解析源码

public interface ReflectorFactory {
  //缓存是否开启
  boolean isClassCacheEnabled();
  //设置缓存开关
  void setClassCacheEnabled(boolean classCacheEnabled);
  //查找对应的缓存信息
  Reflector findForClass(Class<?> 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);
  }
}

environments

环境参数能让我们方便的在测试,开发,生产环境进行切换。其中default参数用来设置默认的环境。

使用方式

<environments default="development">
    <environment id="development">
        <!-- 使用jdbc事务管理 -->
        <transactionManager type="JDBC" />
        <!-- 数据库连接池 -->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver" />
            <property name="url"
                      value="jdbc:mysql://192.168.101.14:3306/test?useUnicode=true&amp;characterEncoding=utf-8" />
            <property name="username" value="jeesite4test" />
            <property name="password" value="jeesite4test" />
        </dataSource>
    </environment>
</environments>

解析源码

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    //先去查找默认的环境id,这里指代上文的development
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    //遍历所有的环境
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      //isSpecifiedEnvironment用来判断default的环境ID是否是当前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中
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

解析事务管理器代码

  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      //别名库中注册了两种类型,
      //typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
      //typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
      TransactionFactory factory = (TransactionFactory) resolveClass(type).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();
    //别名库中有三种类型 EJB 或应用服务器这类容器中使用,使用连接池,不用连接池
    //typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 
    //typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    //typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

databaseIdProvider

数据库厂商标识

使用方式

<databaseIdProvider type="DB_VENDOR" />

解析源码

  private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      //这里是兼容代码
      if ("VENDOR".equals(type)) {
        type = "DB_VENDOR";
      }
      //解析子节点
      Properties properties = context.getChildrenAsProperties();
      //从别名库中获取
      //typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
      databaseIdProvider.setProperties(properties);
    }
    //获取环境,如果不为空而且数据库厂商不为空,那么设置数据库ID
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

typeHandlers

类型处理器,为了将java类型和数据库类型进行映射。列入我们在进行占位符传参的时候,或者我们在查询完结果后,需要将数据库类型和java类型进行转化。

使用方式

方式一:

<typeHandlers>
  <typeHandler handler="com.handelr.ExampleTypeHandler"/>
</typeHandlers>

方式二:

<typeHandlers>
  <package name="com.handelr"/>
</typeHandlers>

也能使用方式一和方式二相结合的方式

解析源码

  private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //解析package节点
        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");
          //从别名库中查找java的类型
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          //从数据库枚举类型中查找对应的类型
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          //将我们配置的handler注册到别名库typeAliasRegistry,并返回对应的class.如果找不到会抛出异常.
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          //最后添加到类型处理器注册中心中
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

包扫描代码,与扫描别名包类似,不在过多分析

  public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    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);
      }
    }
  }

mappers

使用方式

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

解析源码

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      //遍历解析mappers节点
      for (XNode child : parent.getChildren()) {
       //判断节点是否是package
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //其他类型,resource,url,class.且每个mapper只能有其中一种类型,否则到最后的else抛出异常.
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            //resource类型
            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) {
            //url类型
            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类型,直接查找class并添加到configuration中的mapperRegistry里
            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.");
          }
        }
      }
    }
  }

包解析,与解析别名包typeAliases和解析类型处理器typeHandlers的类似,superType也是Object。

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

package的方式最后调用addMapper方法.与解析class的方式相同,最后都是调用MapperRegistry.addMapper。而resource和url的方式调用了XMLMapperBuilder.parse。我们逐个分析。

MapperRegistry.addMapper

  public <T> void addMapper(Class<T> type) {
    //判断该类是否是接口,dao层的类是接口类型的。如果不是那就不做处理,忽略。
    if (type.isInterface()) {
    //如果已经注册过了,抛出异常
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //这里先保存,如果下文parse解析出现异常,再移除
        knownMappers.put(type, new MapperProxyFactory<>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //解析dao对应的xml文件
        parser.parse();
        loadCompleted = true;
      } finally {
         //如果出现异常,移除
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

MapperAnnotationBuilder.parse. MapperAnnotationBuilder类用来解析接口上面的注解

  public void parse() {
    //resource 是dao层接口的路径
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      //这里会根据dao的路径找到xml的路径
      loadXmlResource();
      //添加到set集合中
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      //解析注解@CacheNamespace缓存配置
      parseCache();
      //解析注解@CacheNamespaceRef
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // 判断是否是桥接方法,如果不是,需要进行解析.
          if (!method.isBridge()) {
          //解析该方法上面的注解
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          //如果发生异常,并不是退出,加入到一个解析失败的集合中
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    //该方法对解析失败的方法进行二次解析,如果解析成功,那就移除集合,失败就忽略异常,不做处理.
    parsePendingMethods();
  }

mapper并不是解析失败就抛出异常,而是会加入到一个失败队列中进行多次的重试

MapperAnnotationBuilder.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())) {
    //这里将用类路径来寻找xml文件,两个路径要对应起来才能找到
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          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());
        //最后面调用了XMLMapperBuilder.parse方法
        xmlParser.parse();
      }
    }
  }

XMLMapperBuilder.parse 也是resource和url的方式最后调用的方法

public void parse() {
  //这里解析整个mapper.xml,先判断该文件是否呗解析过
  if (!configuration.isResourceLoaded(resource)) {
    //传入mapper根节点进行解析
    configurationElement(parser.evalNode("/mapper"));
    //添加到集合中
    configuration.addLoadedResource(resource);
    //绑定Namespace里面的Class对象
    bindMapperForNamespace();
  }

  //重新解析之前解析不了的节点
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

如果是resource和url两种方式,在解析完xml文件后就会结束了.然而package和class的两种方式还需要解析接口上面的Mybatis注解.

mapper.xml文件解析

在mapper.xml中,会有一下几种类型的子节点

cache – 该命名空间的缓存配置。
cache-ref – 引用其它命名空间的缓存配置。
resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
sql – 可被其它语句引用的可重用语句块。
insert – 映射插入语句。
update – 映射更新语句。
delete – 映射删除语句。
select – 映射查询语句

这里都是获取子节点值的常规操作,解析对应的节点,并将其添加到Configuration类中

  private void configurationElement(XNode context) {
    try {
      //mapper的缓存信息,命名空间等会被临时保存到MapperBuilderAssistant中,最后把这些公用的信息在存到MappedStatement中
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      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"));
      //在解析增删改查节点时,每个节点都会生成一个mapperStatement对象并保存到configuration类中.
      //mapperStatement保存这这个节点的全部信息,如id,fetchSize,timeout
      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);
    }
  }

XMLMapperBuilder.buildStatementFromContext

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

解析增删改查.在解析增删改查节点时,每个节点都会生成一个mapperStatement对象并保存到配置文件类中。

  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    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);
	//将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。 
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    //替换Includes标签为对应的sql标签里面的值
    includeParser.applyIncludes(context.getNode());

    String parameterType = context.getStringAttribute("parameterType");
    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");
    //引用外部 parameterMap,已废弃
    String parameterMap = context.getStringAttribute("parameterMap");
    //结果类型
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    //引用外部的 resultMap
    String resultMap = context.getStringAttribute("resultMap");
    //结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
    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);
  }

interface mapper接口解析

这里的方法与解析xml的类似,只不过数据是从注解中获取的。不在分析

 void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = configuration.getDefaultResultSetType();
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        if (options.resultSetType() != ResultSetType.DEFAULT) {
          resultSetType = options.resultSetType();
        }
      }