MyBatis啓動之XMLConfigBuilder解析配置文件(二)

ytao

前言

BaseBuilder全部子類

XMLConfigBuilderBaseBuilder(解析中會涉及到講解)的其中一個子類,它的做用是把MyBatis的XML及相關配置解析出來,而後保存到Configuration中。本文就解析過程按照執行順序進行分析,掌握經常使用配置的解析原理。java

使用

調用XMLConfigBuilder進行解析,要進行兩步操做,上篇文章中【MyBatis之啓動分析(一)】有提到。mysql

實例化XMLConfigBuilder對象。

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        // 調用父類的構造方法
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }

實例化Configuration

經過new Configuration()的方式實例化:
new Configuration()
typeAliasRegistry是一個類型別名註冊器,實現原理就是維護一份HashMap,別名做爲key,類的全限定名做爲value。這裏將框架中使用的類註冊到類型別名註冊器中。
TypeAliasRegistry.registerAlias代碼以下:sql

public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    //  在驗證是否存在key和保存kv前,統一將key轉換成小寫
    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 爲定義的一個HashMap
    TYPE_ALIASES.put(key, value);
    }

在實例化Configuration類過程當中,在該類裏除了實例化了TypeAliasRegistry還實例化了另一個下面用到的的類:數據庫

// 類型處理器註冊器
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

TypeHandlerRegistryTypeAliasRegistry實例化邏輯類似,裏面註冊了一些經常使用類型和處理器,代碼易懂。
TypeHandlerRegistry的屬性apache

// jdbc類型和TypeHandler的映射關係,key必須是JdbcType的枚舉類型,讀取結果集數據時,將jdbc類型轉換成java類型
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
    // Java類型與JdbcType類型的鍵值對,存在一對多的映射關係
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
    // 沒有相應的類型處理器時,使用的處理器
    private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
    // 類型處理器類類型和類型處理器的映射關係
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
    // 空處理器的值,用來作校驗
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    // 默認枚舉類型處理器
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

TypeHandlerRegistry構造函數:緩存

public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
    
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());
    
        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());
    
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());
    
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
    
        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());
    
        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());
    
        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());
    
        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
    
        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
    
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
    
        register(InputStream.class, new BlobInputStreamTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());
    
        register(Object.class, UNKNOWN_TYPE_HANDLER);
        register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
    
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
    
        // mybatis-typehandlers-jsr310
        // 是否包含日期,時間相關的Api,經過判斷是否加載java.time.Clock做爲依據
        if (Jdk.dateAndTimeApiExists) {
          this.register(Instant.class, InstantTypeHandler.class);
          this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
          this.register(LocalDate.class, LocalDateTypeHandler.class);
          this.register(LocalTime.class, LocalTimeTypeHandler.class);
          this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
          this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
          this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
          this.register(Month.class, MonthTypeHandler.class);
          this.register(Year.class, YearTypeHandler.class);
          this.register(YearMonth.class, YearMonthTypeHandler.class);
          this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
        }
    
        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());
    }

裏面調用了兩個register()重載方法, type + handler 參的TypeHandlerRegistry.register(Class<T> javaType, TypeHandler<? extends T> typeHandler)type + jdbc type + handler 參的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)mybatis

// java type + handler
    public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type) javaType, typeHandler);
    }
    
    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) {
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
          }
        } else {
          register(javaType, null, typeHandler);
        }
    }
    
    // java type + jdbc type + handler
    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type) type, jdbcType, handler);
    }
    
    // type + handler 和 type + jdbc type + handler 最終都調用此方法
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
          // 當 javaType 不爲空時, 獲取 java 類型的的映射
          Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            // 若映射爲空,新建一個映射關係
            map = new HashMap<JdbcType, TypeHandler<?>>();
            // 保存至類型處理器映射關係中
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          // 保存jdbcType和處理器關係,完成 java類型,jdbc類型,處理器三者之間的註冊
          map.put(jdbcType, handler);
        }
        // 保存處理器信息中
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }
           
    // MappedJdbcTypes 註解        
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MappedJdbcTypes {
      JdbcType[] value();
      boolean includeNullJdbcType() default false;
    }
  • type + handler方法:先獲取處理器的MappedJdbcTypes註解(自定義處理器註解),若註解的value值不爲空時,因爲該值爲JdbcType[]類型,因此for循環 javaType+jdbcType+TypeHandler註冊,若includeNullJdbcTypejdbcType是否包含null)爲true,默認值爲false,註冊到相應映射中。若註解的valuenull,直接調用註冊操做,裏面不會註冊type + jdbc type + handler關係。
  • type + jdbc type + handler方法:該方法將java類強制轉換爲java.lang.reflect.Type類型,而後調用最終註冊的方法。

調用父類BaseBuilder的構造方法

BaseBuilder定義有三個屬性oracle

protected final Configuration configuration;
    // 類型別名註冊器
    protected final TypeAliasRegistry typeAliasRegistry;
    // 類型處理器註冊器
    protected final TypeHandlerRegistry typeHandlerRegistry;

BaseBuilder構造方法app

public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }

這裏屬性,就是上面講解到的。框架

調用 XMLConfigBuilder.parse() 做爲解析入口。

parse()實現配置文件是否解析過

public Configuration parse() {
        // 若parsed爲true,配置文件解析過
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 標誌已解析過
        parsed = true;
        // 從根節點 configuration 開始解析
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

解析/configuration裏的配置

private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          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);
        }
    }

從上面源碼中,不難看出這裏是解析/configuration中的各個子節點。

properties 節點解析

properties配置方式
<!-- 方法一 -->
    <properties>
        <property name="username" value="${jdbc.username}" />
    </properties>
    
    <!-- 方法二 -->
    <properties resource="xxxConfig.properties">
    </properties>
    
    <!-- 方法三 -->
    <properties url="file:///D:/xxxConfig.properties">
    </properties>
propertiesElement()方法
private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          // 獲取 propertie 節點,並保存 Properties 中
          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.");
          }
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          // 將解析的值保存到 XPathParser 中
          parser.setVariables(defaults);
          // 將解析的值保存到 Configuration 中
          configuration.setVariables(defaults);
        }
    }

從上面源碼中,resourceurl的配置形式不容許同時存在,不然拋出BuilderException異常。先解析propertie的配置值,再解析resourceurl的值。
propertie存在與resourceurl相同的key時,propertie的配置會被覆蓋,應爲Properties實現的原理就是繼承的Hashtable類來實現的。

settings 節點解析

settings配置方式
<settings>
        <setting name="cacheEnabled" value="true" />
        ......
    </settings>

設置中各項的意圖、默認值 (引用來源:w3cschool)

設置參數 描述 有效值 默認值
cacheEnabled 該配置影響的全部映射器中配置的緩存的全局開關。 true,false true
lazyLoadingEnabled 延遲加載的全局開關。當開啓時,全部關聯對象都會延遲加載。 特定關聯關係中可經過設置fetchType屬性來覆蓋該項的開關狀態。 true,false false
aggressiveLazyLoading 當啓用時,對任意延遲屬性的調用會使帶有延遲加載屬性的對象完整加載;反之,每種屬性將會按需加載。 true,false,true
multipleResultSetsEnabled 是否容許單一語句返回多結果集(須要兼容驅動)。 true,false true
useColumnLabel 使用列標籤代替列名。不一樣的驅動在這方面會有不一樣的表現, 具體可參考相關驅動文檔或經過測試這兩種不一樣的模式來觀察所用驅動的結果。 true,false true
useGeneratedKeys 容許 JDBC 支持自動生成主鍵,須要驅動兼容。 若是設置爲 true 則這個設置強制使用自動生成主鍵,儘管一些驅動不能兼容但仍可正常工做(好比 Derby)。 true,false False
autoMappingBehavior 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示取消自動映射;PARTIAL 只會自動映射沒有定義嵌套結果集映射的結果集。 FULL 會自動映射任意複雜的結果集(不管是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
defaultExecutorType 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 設置超時時間,它決定驅動等待數據庫響應的秒數。 Any positive integer Not Set (null)
safeRowBoundsEnabled 容許在嵌套語句中使用分頁(RowBounds)。 true,false False
mapUnderscoreToCamelCase 是否開啓自動駝峯命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的相似映射。 true, false False
localCacheScope MyBatis 利用本地緩存機制(Local Cache)防止循環引用(circular references)和加速重複嵌套查詢。 默認值爲 SESSION,這種狀況下會緩存一個會話中執行的全部查詢。 若設置值爲 STATEMENT,本地會話僅用在語句執行上,對相同 SqlSession 的不一樣調用將不會共享數據。 SESSION,STATEMENT SESSION
jdbcTypeForNull 當沒有爲參數提供特定的 JDBC 類型時,爲空值指定 JDBC 類型。 某些驅動須要指定列的 JDBC 類型,多數狀況直接用通常類型便可,好比 NULL、VARCHAR 或 OTHER。 JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER OTHER
lazyLoadTriggerMethods 指定哪一個對象的方法觸發一次延遲加載。 A method name list separated by commas equals,clone,hashCode,toString
defaultScriptingLanguage 指定動態 SQL 生成的默認語言。 A type alias or fully qualified class name. org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
callSettersOnNulls 指定當結果集中值爲 null 的時候是否調用映射對象的 setter(map 對象時爲 put)方法,這對於有 Map.keySet() 依賴或 null 值初始化的時候是有用的。注意基本類型(int、boolean等)是不能設置成 null 的。 true,false false
logPrefix 指定 MyBatis 增長到日誌名稱的前綴。 Any String Not set
logImpl 指定 MyBatis 所用日誌的具體實現,未指定時將自動查找。 SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING Not set
proxyFactory 指定 Mybatis 建立具備延遲加載能力的對象所用到的代理工具。 CGLIB JAVASSIST CGLIB
settingsAsProperties()方法
private Properties settingsAsProperties(XNode context) {
        if (context == null) {
          return new Properties();
        }
        // 獲取setting節點的name和value,並保存至Properties返回
        Properties props = context.getChildrenAsProperties();
        // Check that all settings are known to the configuration class
        // 建立Configuration的MetaClass
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        // 校驗Configuration中是否有setting設置的name值
        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;
    }

這裏獲取到setting的值,並返回Properties對象。而後作配置的name是否合法。
org.apache.ibatis.reflection.MetaClass類是保存着一個利用反射獲取到的類信息,metaConfig.hasSetter(String.valueOf(key))是判斷metaConfig對象中是否包含key屬性。

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

該方法是解析虛擬文件系統配置,用來加載自定義虛擬文件系統的資源。類保存在Configuration.vfsImpl中。

settingsElement()方法

這個方法的做用就是將解析的settings設置到 configuration

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

typeAliases 節點解析

typeAliases配置方式
<typeAliases>
        <package name="com.ytao.main.model"/>
        // 或
        <typeAlias type="com.ytao.main.model.Student" alias="student"/>
        <typeAlias type="com.ytao.main.model.Person"/>
    </typeAliases>

該節點是配置類和別名的關係

  1. package節點是配置整個包下的類
  2. typeAlias節點是指定配置單個類,type爲必填值且爲類全限定名,alias爲選填。
    配置後,是該類時,可直接使用別名。
typeAliasesElement()方法
private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              // 以 package 方式配置
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
              // 以 alias 方式配置
              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);
              }
            }
          }
        }
    }
使用 package 配置

當掃描package時,獲取到包名後TypeAliasRegistry.registerAliases(typeAliasPackage)

public void registerAliases(String packageName){
        registerAliases(packageName, Object.class);
    }

    public void registerAliases(String packageName, Class<?> superType){
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        // 獲取 package 下全部已 .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);
          }
        }
    }

掃描到指定package下全部以.class結尾文件的類,並保存至Set集合中,而後遍歷集合,過濾掉沒有名稱,接口,和底層特定類。
最後TypeAliasRegistry.registerAlias(Class<?> type)註冊到別名註冊器中。

public void registerAlias(Class<?> type) {
        // 使用類的 simpleName 做爲別名,也就是默認的別名命名規則
        String alias = type.getSimpleName();
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
          alias = aliasAnnotation.value();
        } 
        // 上面分析的最終註冊的方法
        registerAlias(alias, type);
    }

經過類註冊到註冊器中時,若是該註冊類有使用@Aliasorg.apache.ibatis.type.Alias)註解,那麼XML配置中配置的別名會被註解配置覆蓋。

使用 typeAlias 配置

若是typeAliasalias有設置值,使用自定名稱方式註冊,不然使用默認方式註冊,即類的simpleName做爲別名。

plugins 節點解析

plugins配置方式
<plugins>
        // 配置自定義插件,可指定在某個點進行攔截
        <plugin interceptor="com.ytao.main.plugin.DemoInterceptor">
            // 當前插件屬性
            <property name="name" value="100"/>
        </plugin>
    </plugins>

自定義插件須要實現org.apache.ibatis.plugin.Interceptor接口,同時在註解上指定攔截的方法。

pluginElement()方法
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 interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // 設置插件屬性到插件中
            interceptorInstance.setProperties(properties);
            // 將插件保存在 configuration 中
            configuration.addInterceptor(interceptorInstance);
          }
        }
    }

這裏取<plugin>節點的interceptor可使用別名設置。從源碼中resolveClass方法

// 
    protected Class<?> resolveClass(String alias) {
        if (alias == null) {
          return null;
        }
        try {
          return resolveAlias(alias);
        } catch (Exception e) {
          throw new BuilderException("Error resolving class. Cause: " + e, e);
        }
    }
    
    // 
    protected Class<?> resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }
    
    // 
    public <T> Class<T> resolveAlias(String string) {
        try {
          if (string == null) {
            return null;
          }
          // issue #748
          // 將傳入的 類 名稱統一轉換
          String key = string.toLowerCase(Locale.ENGLISH);
          Class<T> value;
          // 驗證別名中是否有當前傳入的key
          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);
        }
    }

以上源碼爲別名解析過程,其餘別名的解析也是調用此方法,先去保存的別名中去找,是否有別名,若是沒有就經過Resources.classForName生成實例。

objectFactory,objectWrapperFactory,reflectorFactory 節點解析

以上都是對實現類都是對MyBatis進行擴展。解析方法也相似,最後都是保存在configuration

// objectFactory 解析
    private void objectFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties properties = context.getChildrenAsProperties();
          ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
          factory.setProperties(properties);
          configuration.setObjectFactory(factory);
        }
    }
    
    // objectWrapperFactory 解析
    private void objectWrapperFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
          configuration.setObjectWrapperFactory(factory);
        }
    }
    
    // reflectorFactory 解析
    private void reflectorFactoryElement(XNode context) throws Exception {
        if (context != null) {
           String type = context.getStringAttribute("type");
           ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
           configuration.setReflectorFactory(factory);
        }
    }

以上爲解析objectFactory,objectWrapperFactory,reflectorFactory源碼,通過前面的分析後,這裏比較容易看懂。

environments 節點解析

environments配置方式
<environments default="development">
        <environment id="development">
            <!-- 事務管理 -->
            <transactionManager type="JDBC">
                <property name="prop" value="100"/>
            </transactionManager>
            <!-- 數據源 -->
            <dataSource type="UNPOOLED">
                <!-- JDBC 驅動 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- 數據庫的 url -->
                <property name="url" value="${jdbc.url}"/>
                <!-- 數據庫登陸名 -->
                <property name="username" value="${jdbc.username}"/>
                <!-- 數據庫登陸密碼 -->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <!-- 一個環境,對應一個environment -->
        ......
    </environments>

該節點可設置多個環境,針對不一樣的環境單獨配置。environments的屬性default是默認環境,該值對應一個environment的屬性id的值。

  • transactionManager爲事務管理,屬性type爲事務管理類型,上面的介紹的new Configuration()有定義類型有:JDBC 和 MANAGED事務管理類型。
  • dataSource是數據源,type爲數據源類型,與transactionManager同理,可知內建的數據源類型有:JNDI,POOLED,UNPOOLED數據源類型。
environmentsElement()方法
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");
            // 驗證 id
            if (isSpecifiedEnvironment(id)) {
              // 解析 transactionManager, 並實例化 TransactionFactory
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              // 解析 dataSource,並實例化 DataSourceFactory
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              // 獲取 dataSource
              DataSource dataSource = dsFactory.getDataSource();
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
    }
    
    private boolean isSpecifiedEnvironment(String id) {
        if (environment == null) {
          throw new BuilderException("No environment specified.");
        } else if (id == null) {
          throw new BuilderException("Environment requires an id attribute.");
        } else if (environment.equals(id)) {
          return true;
        }
        return false;
    }

若沒有配置environment環境或環境沒有給id屬性,則會拋出異常,若當前id是要使用的就返回true,不然返回false
TransactionFactory實例化過程比較簡單,與建立DataSourceFactory相似。

數據源的獲取

獲取數據源,首先得建立DataSourceFactory,上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))建立

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties props = context.getChildrenAsProperties();
          DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
          factory.setProperties(props);
          return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }

這裏就是獲取到數據源得type後,利用上面所講到得resolveClass()方法獲取到DataSourceFactory
UNPOOLED爲例,對應的DataSourceFactory實現類爲UnpooledDataSourceFactory。實例化過程當中就給該類的屬性dataSource數據源賦值了

/**
     * UnpooledDataSourceFactory 類
     */
    protected DataSource dataSource;
    
    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }
    
    @Override
    public DataSource getDataSource() {
       return dataSource;
    }

UnpooledDataSource類裏面有靜態代碼塊因此數據源被加載

/**
     * UnpooledDataSource 類
     */
    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
          Driver driver = drivers.nextElement();
          registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }

databaseIdProvider 節點解析

databaseIdProvider配置方式
<databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
        <property name="MySQL" value="mysql"/>
    </databaseIdProvider>
    
    <select id="select" resultType="com.ytao.main.model.Student" databaseId="mysql">
        select
          *
        from student
    </select>

基於映射語句中的databaseId屬性,能夠根據不一樣數據庫廠商執行不一樣的sql。

databaseIdProviderElement()方法
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();
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

根據匹配的數據庫廠商類型匹配數據源databaseIdProvider.getDatabaseId(environment.getDataSource())

@Override
  public String getDatabaseId(DataSource dataSource) {
    if (dataSource == null) {
      throw new NullPointerException("dataSource cannot be null");
    }
    try {
      return getDatabaseName(dataSource);
    } catch (Exception e) {
      log.error("Could not get a databaseId from dataSource", e);
    }
    return null;
  }
  
  private String getDatabaseName(DataSource dataSource) throws SQLException {
    // 根據數據源獲取數據庫產品名稱
    String productName = getDatabaseProductName(dataSource);
    if (this.properties != null) {
      for (Map.Entry<Object, Object> property : properties.entrySet()) {
        // 判斷是否包含,選擇使用的數據庫產品
        if (productName.contains((String) property.getKey())) {
          return (String) property.getValue();
        }
      }
      // no match, return null
      return null;
    }
    return productName;
  }
    
  private String getDatabaseProductName(DataSource dataSource) throws SQLException {
    Connection con = null;
    try {
      // 數據庫鏈接
      con = dataSource.getConnection();
      // 獲取鏈接元數據
      DatabaseMetaData metaData = con.getMetaData();
      // 獲取數據庫產品名稱
      return metaData.getDatabaseProductName();
    } finally {
      if (con != null) {
        try {
          con.close();
        } catch (SQLException e) {
          // ignored
        }
      }
    }
  }

這裏須要注意的是配置:好比使用mysql,我踩過這裏的坑,這裏Name爲MySQL,我把y寫成大寫,結果匹配不上。
另外這裏寫個My也能匹配上,應爲是使用的String.contains方法,只要包含就會符合,這裏代碼應該不夠嚴謹。

typeHandlers 節點解析

typeHandlers配置方式
<typeHandlers>
        <package name="com.ytao.main.handler"/>
        // 或
        <typeHandler javaType="java.util.Date"  jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler" />
    </typeHandlers>

掃描整個包或者指定類型之間的映射,javaType, jdbcType非必需,handler必填項

typeHandlerElement()方法
private void typeHandlerElement(XNode parent) throws Exception {
    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);
          }
        }
      }
    }
  }

源碼分析會根據包下全部處理器或者指定處理器進行解析,最後會根據上面分析到的type + handlertype + jdbc type + handler不一樣狀況註冊。
另外這裏還有個TypeHandlerRegistry.register(Class<?> typeHandlerClass)註冊類

public void register(Class<?> typeHandlerClass) {
    // 標誌是否從 MappedTypes 註解中獲取 javaType 註冊
    boolean mappedTypeFound = false;
    // 獲取 MappedTypes 的值
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        // 已 type + handler 的方式註冊
        register(javaTypeClass, typeHandlerClass);
        // 標誌已經過註解註冊類型
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      // 經過 TypeHandler 註冊
      register(getInstance(null, typeHandlerClass));
    }
  }
  
  // 實例化
  public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        // 獲取有參構造函數
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        // 實例化對象
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
      // 獲取無參構造函數
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }  
  
  // 註冊實例
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }

以上的register方法中,瞭解type + jdbc type + handler後,其餘的register重載方法比較容易理解,其餘的都是基於它上面的封裝。

mappers 節點解析

mappers配置方式
<mappers>
        <package name="com.ytao.main.mapper"/>
        // 或
        <mapper resource="mapper/studentMapper.xml"/>
        // 或
        <mapper url="file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/>
        // 或
        <mapper class="com.ytao.main.mapper.StudentMapper"/>
    </mappers>

可經過以上四種形式配置mappers節點,<package><mapper>爲互斥節點。

mapperElement()方法

該方法是負責解析<mappers>節點

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 若是配置 package 節點,則掃描
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // 解析包下類Mapper接口,並註冊到configuration的mapperRegistry中
          configuration.addMappers(mapperPackage);
        } else {
          // 獲取mapper節點的resource,url,class屬性
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 根據resource解析,而且url,class值必須爲空,也就不能配置值。url,class同理,其它兩個屬性也不能配置值
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // 經過resource獲取流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 建立XMLMapperBuilder對象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析映射配置文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // 經過url獲取流
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 和resource解析方式同樣,建立XMLMapperBuilder對象,而後解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // 加載class屬性的接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 將接口註冊到configuration的mapperRegistry中
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

<package>的包掃描到的類,而後單個單個註冊到configuration的mapperRegistry中,這裏和<mapper>使用class屬性是同樣邏輯。
解析package方式

// Configuration 中定義了
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  /**
   * 步驟一
   * 該函數於 Configuration 中  
   */  
  public void addMappers(String packageName) {
    // mapperRegistry定義在Configuration中的一個屬性
    mapperRegistry.addMappers(packageName);
  }
  
  /**
   * 步驟二
   * 該函數於 MapperRegistry 中  
   */   
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  
  /**
   * 步驟三
   * 該函數於 MapperRegistry 中  
   */       
  public void addMappers(String packageName, Class<?> superType) {
    // 經過 ResolverUtil 獲取包下的類
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 遍歷獲取到的類,註冊到 MapperRegistry
      addMapper(mapperClass);
    }
  }   
   
  /**
   * 步驟四
   * 該函數於 MapperRegistry 中
   */
  public <T> void addMapper(Class<T> type) {
    // mapper 類爲 interface 接口
    if (type.isInterface()) {
      // 判斷當前class是否已經註冊過
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      // 校驗是否加載完成
      boolean loadCompleted = false;
      try {
        // 保存 mapper 接口和 MapperProxyFactory 之間的映射
        knownMappers.put(type, new MapperProxyFactory<T>(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.
        // 解析xml和註解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        // 標誌加載完成
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

解析mapperclass屬性

// 該函數於 Configuration 中  
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  
  // ... 這裏調用上面的【步驟四】

這兩中方式是直接註冊接口到mapperRegistry,另外兩種是解析xml的方式就是獲取映射文件的namespace,再註冊進來,XMLMapperBuilder是負責解析映射配置文件的類,從此會單獨詳細分析這個類,這裏不展開講。

這裏對XMLConfigBuilder解析配置文件到此分析完,本文對配置文件解析的流程大體瞭解流程和原理。相信遇到配置問題異常,大體能排查到根本緣由。




我的博客: https://ytao.top
個人公衆號 ytao
個人公衆號

相關文章
相關標籤/搜索