mybatis源碼閱讀(二):mybatis初始化上

1.初始化入口java

//Mybatis 經過SqlSessionFactory獲取SqlSession, 而後才能經過SqlSession與數據庫進行交互
private static SqlSessionFactory getSessionFactory() {
    SqlSessionFactory sessionFactory = null;
    String resource = "configuration.xml";
    try {
        sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
    } catch (IOException e) {
        e.printStackTrace();
    }
    return sessionFactory;
}

那麼,咱們就先從SqlSessionFactoryBuilder入手, 我們先看看源碼是怎麼實現的數據庫

SqlSessionFactoryBuildersession

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // 讀取配置文件
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //解析配置獲得Configuration對象,建立DefaultSqlSessionFactory對象
    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.
    }
  }
}

XMLConfigBuildermybatis

XMLConfigBuilder是BaseBuilder的衆多子類之一,核心字段以下app

//表示是否已經解析過了
private boolean parsed;
//用於解析配置文件的對象
private final XPathParser parser;
//配置文件中表示<environment>的名稱  默認讀取default屬性
private String environment;
// 負責和建立Reflector對象
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

XMLConfigBuilder.parse()方法是解析mybatis-config.xml配置文件的如。它調用parseConfiguration()方法實現整個解析過程。具體實現以下:ide

/**
   * 解析配置文件的入口
   * @return
   */
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

  /**
   * 對配置文件每一個節點具體的解析過程
   * configuration節點爲根節點。
   * 在configuration節點之下,咱們能夠配置11  個子節點,
   * 分別爲:properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、
   * environments、databaseIdProvider、typeHandlers、mappers。
   * @param root 根節點
   */
private void parseConfiguration(XNode root) {
  try {
      // 解析properties節點
    propertiesElement(root.evalNode("properties"));
      //解析settings節點
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);//設置vfsImpl字段
      //解析typeAliases節點
    typeAliasesElement(root.evalNode("typeAliases"));
      //解析plugins節點
    pluginElement(root.evalNode("plugins"));
      //解析objectFactory節點
    objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory節點
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析reflectorFactory節點
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
      //解析environments節點
    environmentsElement(root.evalNode("environments"));
      //解析databaseIdProvider節點
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析typeHandlers節點
    typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers節點
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

propertiesElement()方法會解析配置文件中的properties節點並造成Java.util.Properties對象,以後將改對象設置到XpathParse和Configguration的variables字段中,佔位符就是用Properties中的信息替換的,具體實現以下:ui

/**
   * 解析properties的具體方法
   * @param context
   * @throws Exception
   */
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
      // 將子節點的 name 以及value屬性set進properties對象
      // 這兒能夠注意一下順序,xml配置優先, 外部指定properties配置其次
       Properties defaults = context.getChildrenAsProperties();
      // 獲取properties節點上 resource屬性的值
      String resource = context.getStringAttribute("resource");
      // 獲取properties節點上 url屬性的值, resource和url不能同時配置
      String url = context.getStringAttribute("url");
    if (resource != null && url != null) {//url 和resource不能同時配置
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
      // 把解析出的properties文件set進Properties對象
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
      // 將configuration對象中已配置的Properties屬性與剛剛解析的融合
      // configuration這個對象會裝載所解析mybatis配置文件的全部節點元素,之後也會頻頻提到這個對象
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
      // 把裝有解析配置propertis對象set進解析器, 由於後面可能會用到
    parser.setVariables(defaults);
      // set進configuration對象
    configuration.setVariables(defaults);
  }
}

settings節點下的配飾是mybatis的全局性配置,修改的是configuration對象的屬性,具體說明參考官方文檔url

/**
 * settings標籤就是設置configuration對象的各類屬性,
 * 具體屬性說明能夠參考mybatis官方文檔
 * @param props
 * @throws Exception
 */
private void settingsElement(Properties props) throws Exception {
  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.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")));
  @SuppressWarnings("unchecked")
  Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
  configuration.setDefaultEnumTypeHandler(typeHandler);
  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"));
  @SuppressWarnings("unchecked")
  Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
  configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

environments元素節點主要配置數據庫事物,數據源。能夠配置多個environment子節點,假如咱們系統的開發環境和正式環境所用的數據庫不同(這是確定的), 那麼能夠設置兩個environment, 兩個id分別對應開發環境(development)和正式環境(final),那麼經過配置environments的default屬性就能選擇對應的environment了, 例如,我將environments的deault屬性的值配置爲development, 那麼就會選擇dev的environment。具體實現以下插件

/**
   * 解析enviroments元素節點的方法
   * @param context
   * @throws Exception
   */
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      //獲取 <environments default="development"> 中的default值
      environment = context.getStringAttribute("default");
    }
    // 循環environments的子節點
    for (XNode child : context.getChildren()) {
      // 獲取 <environment id="development"> z中的id
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {//根據由environments的default屬性去選擇對應的enviroment
        // 事物 mybatis有兩種:JDBC 和 MANAGED, 配置爲JDBC則直接使用JDBC的事務,配置爲MANAGED則是將事務託管給容器
        // <transactionManager type="JDBC"/>
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //enviroment節點下面就是dataSource節點了,解析dataSource節點
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // 將dataSource設置進configuration對象
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

typeAliases節點主要用來設置別名,其實這是挺好用的一個功能, 經過配置別名,咱們不用再指定完整的包名xml

/**
 * 解析typeAliases 節點
 * <typeAliases>
 *     <!--<package name="com.lpf.entity"></package>-->
 *     <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>
 * </typeAliases>
 * @param parent
 */
private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //若是子節點是package, 那麼就獲取package節點的name屬性, mybatis會掃描指定的package
      if ("package".equals(child.getName())) {
        String typeAliasPackage = child.getStringAttribute("name");
        //TypeAliasRegistry 負責管理別名, 這兒就是經過TypeAliasRegistry 進行別名註冊
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
          //若是子節點是typeAlias節點,那麼就獲取alias屬性和type的屬性值
        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);
        }
      }
    }
  }
}

具體的別名註冊類

public class TypeAliasRegistry {

  // 別名經過一個HashMap來實現, key爲別名, value就是別名對應的類型(class對象)
  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  /**
   * mybatis默認爲咱們註冊的別名
   */
  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

  /**
   * 處理別名, 直接從保存有別名的hashMap中取出便可
   */
  @SuppressWarnings("unchecked")
  // throws class cast exception as well if types cannot be assigned
  public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // issue #748
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }


    /**
     * 配置文件中配置爲package的時候,掃描包下的Javabean ,而後自動註冊別名
     * 默認會使用 Bean 的首字母小寫的非限定類名來做爲它的別名
     * 也可在javabean 加上註解@Alias 來自定義別名, 例如: @Alias(user)
     */
  public void registerAliases(String packageName){
    registerAliases(packageName, Object.class);
  }

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

  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
  }

    //向hashMap中註冊別名
  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 (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    TYPE_ALIASES.put(key, value);
  }

  public void registerAlias(String alias, String value) {
    try {
      registerAlias(alias, Resources.classForName(value));
    } catch (ClassNotFoundException e) {
      throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
    }
  }

    /**
     * 獲取保存別名的HashMap, Configuration對象持有對TypeAliasRegistry的引用,
     * 所以,若是須要,咱們能夠經過Configuration對象獲取
     */
  public Map<String, Class<?>> getTypeAliases() {
    return Collections.unmodifiableMap(TYPE_ALIASES);
  }

}

typeHandlers節點的解析和typeAlianses節點的解析相似

/**
   * 解析typeHandlers節點
   * 不管是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,
   * 仍是從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。
   * Mybatis默認爲咱們實現了許多TypeHandler, 當咱們沒有配置指定TypeHandler時,
   * Mybatis會根據參數或者返回結果的不一樣,默認爲咱們選擇合適的TypeHandler處理。
   * @param parent
   * @throws Exception
   */
private void typeHandlerElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
        //子節點爲package時,獲取其name屬性的值,而後自動掃描package下的自定義typeHandler
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
          //子節點爲typeHandler時, 能夠指定javaType屬性, 也能夠指定jdbcType, 也可二者都指定
          //javaType 是指定java類型
          //jdbcType 是指定jdbc類型(數據庫類型: 如varchar)
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
          //handler就是咱們配置的typeHandler
        String handlerTypeName = child.getStringAttribute("handler");
        Class<?> javaTypeClass = resolveClass(javaTypeName);
          //JdbcType是一個枚舉類型,resolveJdbcType方法是在獲取枚舉類型的值
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          //註冊typeHandler, typeHandler經過TypeHandlerRegistry這個類管理
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

插件是mybatis提供的擴展機制,用戶能夠經過添加自定義插件在SQL語句執行的過程當中某一環節進行攔截,mybatis中的自定義插件只需實現Interceptor接口,並經過註解指定攔截的方法簽名,這個後面具體介紹。

/**
   * 解析plugins標籤
   * mybatis中的plugin其實就是個interceptor,
   * 它能夠攔截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 的部分方法,處理咱們本身的邏輯。
   * @param parent
   * @throws Exception
   */
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的時候,須要去實現Interceptor
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      interceptorInstance.setProperties(properties);
      // 向configuration對象中註冊攔截器
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

mybatis初始化時,出了加載mybatis-config.xml的全局配置文件,還會加載所有的映射配置文件,即mappers節點配置的mapper.

/**
   * 解析mapper文件,mapper能夠理解爲dao的實現
   * @param parent
   * @throws Exception
   */
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
        //若是mappers節點的子節點是package, 那麼就掃描package下的文件, 注入進configuration
      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");
          //resource, url, class 三選一
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
            //mapper映射文件都是經過XMLMapperBuilder解析
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          //同上
          XMLMapperBuilder 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.");
        }
      }
    }
  }
}

mybatis初始化過程當中對mybatis-config.xml配置文件的解析過程到這吧,下一個就叫啥mapper配置文件的解析過程。

相關文章
相關標籤/搜索