Mybatis源码分析三之Configuration

一、Configuration

Configuration对应的就是mybatis的配置文件,是mybatis的核心类之一,影响着mybatis的各种设置和运行行为,运行mybatis的第一步就是把配置文件转换成Configuration对象。
回到SqlSessionFactoryBuilder的build方法,configuration配置文件的解析是通过XMLConfigBuilder来实现。调用其parse()方法返回Configuration对象。

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //parser=XPathParser
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  private void parseConfiguration(XNode root) {
    try {
      //会把所有的properties的属性值存入的Configuration的variables属性
      propertiesElement(root.evalNode("properties"));
      //读取settings
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //配置自定义的VFS实现类,多个以逗号分隔
      loadCustomVfs(settings);
      //设置日志的具体实现这些都在官网可查
      loadCustomLogImpl(settings);
      //把类型别名添加到typeAliasRegistry属性中
      typeAliasesElement(root.evalNode("typeAliases"));
      //把插件添加到interceptorChain属性中
      pluginElement(root.evalNode("plugins"));
      //设置objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));	
      //设置objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //设置reflectorFactory
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //配置所有的settings值
      settingsElement(settings);
      //解析environments
      environmentsElement(root.evalNode("environments"));
      //设置数据库提供商标识
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //把类型处理器注册到typeHandlerRegistry
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers文件具体的解析过程是交给XMLMapperBuilder来实现
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这里就是一步步去解析配置文件的过程。
propertiesElement(root.evalNode(“properties”)):
从配置来看,有三种方式,一是设置resource或者url属性文件路径,还有就是直接设置键值对,resources和url只能二选一,不能同时配置,所以该方法会把这几种属性配置方式的配置的值都解析成一个Properties对象,然后设置到Configuration对象的variables属性。
loadCustomVfs(settings):
会根据settings配置的vfsImpl值(类的全限定名多个以逗号隔开),转换成class对象,然后添加到mybatis自动的VSF列表中。
loadCustomLogImpl(settings):
配置mybatis日志的具体实现,可取值SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING,Configuration对象会默认添加这些类型别名到typeAliasRegistry中,所以该方法就是根据你配置的日志实现找到具体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);

typeAliasesElement(root.evalNode(“typeAliases”)):
解析类型别名到typeAliasRegistry,意思就是我们用了这个类型别名之后 就可以在需要用这个类的时候,直接用别名就可以了,而不需要去写他的全限定名,我们也可以直接配置一个package,这样mybatis就会自动到包名下去搜索。
pluginElement(root.evalNode(“plugins”)):
源码逻辑很简单就是把配置好的interceptor实例化,然后添加到interceptorChain对象中保存到interceptors数组中,这里需要注意的是这些都是mybatis中对插件的配置。
插件就是在mybatis执行映射语句的时候对一些方法进行拦截调用,也就是你可以针对你需要拦截的方法编写对应的插件,只需要实现Interceptor 接口,然后配置好对应的需要拦截的方法签名即可,官网有样例。mybatis默认可以拦截的方法有:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

objectFactoryElement(root.evalNode(“objectFactory”)):
配置ObjectFactory,他的默认实现是DefaultObjectFactory,如果你想修改一些实例的属性值,可以实现这个接口然后在配置文件中配置好,然后就可以操作。Configuration会设置到objectFactory属性。
objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”)):
这是对象包装工厂,一般不怎么使用,除非你有特殊类需要特殊处理的时候,才会用到。
reflectorFactoryElement(root.evalNode(“reflectorFactory”)):
设置反射工厂,可以替代默认的实现
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")));
    configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
    configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
  }

environmentsElement(root.evalNode(“environments”)):
解析Environment对象

  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");
        if (isSpecifiedEnvironment(id)) {
        //获取JDBC或者MANAGED事务工厂,这已经分析过
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          /**获取数据源的工厂,根据<dataSource type="POOLED">配置type类型
            typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    		   typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    		   typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    		   找到对应的 数据源工厂,并且获取到dataSource标签下所有的属性,并转换成Properties对象**/
          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());
          break;
        }
      }
    }
  }

DataSourceFactory有三个实现,分别是JndiDataSourceFactory、PooledDataSourceFactory、UnpooledDataSourceFactory,其中PooledDataSourceFactory继承自UnpooledDataSourceFactory,都是为了得到DataSource实例。简单分析一下UnpooledDataSourceFactory:
从dataSourceElement方法可知,会调用DataSourceFactory的setProperties方法:

  public void setProperties(Properties properties) {
    Properties driverProperties = new Properties();
    //把数据源封装成元数据对象
    MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
    //接下来就是通过元数据对象来设置配置的属性到原对象中,也就是调用其setter方法
    for (Object key : properties.keySet()) {
      String propertyName = (String) key;
      if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
        String value = properties.getProperty(propertyName);       driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
      } else if (metaDataSource.hasSetter(propertyName)) {
        String value = (String) properties.get(propertyName);
        Object convertedValue = convertValue(metaDataSource, propertyName, value);
        metaDataSource.setValue(propertyName, convertedValue);
      } else {
        throw new DataSourceException("Unknown DataSource property: " + propertyName);
      }
    }
    if (driverProperties.size() > 0) {
      metaDataSource.setValue("driverProperties", driverProperties);
    }
  }

上边的方法借助MetaObject 对象通过反射的方式把数据库连接的url、driver、username等信息配置到对应的属性。
注意:在配置environments时,可以配置多个数据源,但是具体使用哪个数据源是根据配置的environment id="development"和environments default="development"两个配置的值相等即可
databaseIdProviderElement(root.evalNode(“databaseIdProvider”)):
解析数据库提供者标签,可以通过标签来配置,Configuration对象中实际保存的是databaseId字符串,也就是会根据数据源得到当前使用的数据库产品,比如mysql、oracle等。
释义:不同的数据库提供商,对于语句的执行方面也会有差异,比如分页的实现、时间函数等操作,所以在编写映射文件(mapper.xml)的时候就会存在这种问题,同一个操作(比如都是查询时间)但是不同数据库语句不一样,所以此时我们可以在映射文件中添加databaseId属性,来标识不同的数据库,如果配置文件中配置了databaseIdProvider,mybatis就会加载所有不带带和匹配带有databaseId的语句,如果带和不带都有,则不带会被舍弃(也就是语句中的databaseId和当前数据库匹配上后,不带databaseId的才会被舍弃)。
typeHandlerElement(root.evalNode(“typeHandlers”)):
类型处理器:主要是在PreparedStatement对参数或结果集处理的时候,用类型转换器转换成对应的Java类型,所以客户可以编写自己的typehandler来处理对应的数据类型,最后都会被注册到Configuration 的typeHandlerRegistry中。
mapperElement(root.evalNode(“mappers”)):
加载mapper文件,配置文件中可以指定一个包名,或者文件路径以及类的全限定名等多种方式。如果采用文件路径配置方式,则会交给XMLMapperBuilder来解析,其余两种都是直接调用addMapper方法或者addMappers方法,最终都会代用MapperRegistry的addMapper方法。

  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 {
      //添加到map集合中,键是mapper接口,值是其代理工厂类,记住即可
        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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();//解析xml文件以及注解方式
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

mapper.xml映射文件的解析过程单独继续分析。

二、总结

Configuration对象会把配置文件的各种信息解析成java对象,以供后续mybatis的调用,这也是mybatis的核心,所以有必要知道每个配置在Configuration对象中的存在形式,方便后续源码分析的时候能更加简单。

以上,有任何不对的地方,请指正,敬请谅解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值