(一) Mybatis原始碼分析-解析器模組

草人木發表於2020-04-19

Mybatis原始碼分析-解析器模組

原創-轉載請說明出處

1. 解析器模組的作用

  • 對XPath進行封裝,為mybatis-config.xml配置檔案以及對映檔案提供支援
  • 為處理動態 SQL 語句中的佔位符提供支援

2. 解析器模組parsing包

3. 解析器模組parsing包

  • GenericTokenParser
  • package-info.java
  • ParsingException
  • PropertyParser
  • TokenHandler
  • XNode
  • XPathParser

mybati-config.xml檔案

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- autoMappingBehavior should be set in each test case -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value=""/>
            </transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="org.hsqldb.jdbcDriver"/>
                <property name="url" value="jdbc:hsqldb:mem:automapping"/>
                <property name="username" value="sa"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
    </mappers>

</configuration>

4. mybatis 解析mybatis-config.xml過程

4.1 分析步驟

  • 開啟mybatis原始碼專案;
  • 進入單元測試目錄下:org.apache.ibatis.autoconstructor.AutoConstructorTest,方法:fullyPopulatedSubject();
  • 除錯模式下執行測試方法fullyPopulatedSubject(),斷點觀察,mybatis是如何對mybatis-config.xml配置檔案進行解析的。

4.2 程式碼過程解析

第一步:建立SqlSessionFactory的整體過程
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) {
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}

    上面程式碼中,mybatis通過提供一個Resources的工具類來載入配置檔案,獲取輸入流。然後再通過SqlSessionFactoryBuilder的builder方法來構建SqlSessionFactory物件,其中builder方法裡的實現就是對mybatis-config.xml配置檔案進行解析的入口。下面的程式碼就是builder方法的具體實現:

1.呼叫builder方法
public SqlSessionFactory build(Reader reader) {
   return build(reader, null, null);
}

2.呼叫builder過載方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      //2.1> 建立配置檔案解析器
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //2.2> 呼叫parse方法解析配置檔案,將對應的屬性存入並生成Configuration物件
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

3.通過Configuration建立SqlSessionFactory 
public SqlSessionFactory build(Configuration config) {
  //建立DefaultSqlSessionFactory物件
  return new DefaultSqlSessionFactory(config);
}

    上面程式碼中展示了建立SqlSessionFactory 的整體流程,其中最重要的是2.1:建立配置檔案解析器2.2:呼叫parse方法解析配置檔案,將對應的屬性存入並生成Configuration物件

第二步:建立配置檔案解析器XMLConfigBuilder

XMLConfigBuilder.class

/**
 * 構造XMLConfigBuilder
 * @param inputStream 輸入流
 * @param environment environment環境
 * @param props Properties properties物件
 */
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
/**
 * 構造XMLConfigBuilder
 * @param parser XPathParser
 * @param environment environment環境
 * @param props properties物件
 */
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
  //呼叫父類(BaseBuilder)建構函式,建立Configuration:會進行別名註冊
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  //Configuration設定properties
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

    上面的程式碼就是構建XMLConfigBuilder的整體過程,該過程會構建XPathParser,XPathParser主要用來解析XML封裝了Document、EntityResolver 和XPath等物件,提供了一系列的解析XML的方法。
    下面的程式碼是XPathParser構建的程式碼,使用到了工程模式去建立XPath物件。

XPathParser.class

/**
 * 構造 XPathParser 物件
 *
 * @param inputStream inputStream 輸入流
 * @param validation 是否校驗 XML
 * @param variables 變數 Properties 物件
 * @param entityResolver XML 實體解析器
 */
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
  commonConstructor(validation, variables, entityResolver);
  this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
  this.validation = validation;
  this.entityResolver = entityResolver;
  this.variables = variables;
  XPathFactory factory = XPathFactory.newInstance();
  this.xpath = factory.newXPath();
}

  • 建立 Document 物件,Document 物件代表整個 XML 文件。這裡會解析XML輸入源得到Document物件。
/**
* 建立 Document 物件
*
* @param inputSource XML 的 InputSource 物件
* @return Document 物件
*/
private Document createDocument(InputSource inputSource) {
 // important: this must only be called AFTER common constructor
 try {
   // 1> 建立DocumentBuilderFactory物件
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   factory.setValidating(validation);// 設定是否檢驗XML

   factory.setNamespaceAware(false);
   factory.setIgnoringComments(true);
   factory.setIgnoringElementContentWhitespace(false);
   factory.setCoalescing(false);
   factory.setExpandEntityReferences(true);

   //2> 建立DocumentBuilder物件
   DocumentBuilder builder = factory.newDocumentBuilder();
   builder.setEntityResolver(entityResolver);// 設定實體解析器
   builder.setErrorHandler(new ErrorHandler() {// 實現都是空的
     @Override
     public void error(SAXParseException exception) throws SAXException {
       throw exception;
     }

     @Override
     public void fatalError(SAXParseException exception) throws SAXException {
       throw exception;
     }

     @Override
     public void warning(SAXParseException exception) throws SAXException {
     }
   });
   //3> 解析XML檔案 返回Document物件
   return builder.parse(inputSource);
 } catch (Exception e) {
   throw new BuilderException("Error creating document instance.  Cause: " + e, e);
 }
}

第三步:建立配置檔案解析器XMLConfigBuilder

    通過XMLConfigBuilder的parse()去解析mybatis-config.xml配置檔案並返回Configuration物件。

XMLConfigBuilder.class

//1> 建立配置檔案解析器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//2> 呼叫parse方法解析配置檔案,將對應的屬性存入並生成Configuration物件
return build(parser.parse());
/**
 * 解析mybatis-config.xml
 * @return configuration
 */
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析configuration節點<configuration></configuration>
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

XPathParser是解析xml的核心

XPathParser.class

/**
 * 獲取Document中符合xpath表示式:expression的XNODE物件
 * @param expression xpath表示式
 * @return XNode
 */
public XNode evalNode(String expression) {
  return evalNode(document, expression);
}
/**
 * 獲取XNode物件
 * @param root Document xml 物件
 * @param expression xpath表示式
 * @return XNode
 */
public XNode evalNode(Object root, String expression) {
  //1> 獲得Node物件
  Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
  if (node == null) {
    return null;
  }
  //2> 封裝成XNode物件
  return new XNode(this, node, variables);
}
/**
 * 獲得指定元素或節點的值
 * 並返回指定型別的結果
 *
 * @param expression 表示式
 * @param root 指定節點
 * @param returnType 返回型別
 * @return 值
 */
private Object evaluate(String expression, Object root, QName returnType) {
  try {
    // 獲取指定上下文中的 XPath 表示式並返回指定型別的結果。
    return xpath.evaluate(expression, root, returnType);
  } catch (Exception e) {
    throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
  }
}
第四步:解析mybatis-config.xml中configuration節點下配置資訊

主要程式碼:parseConfiguration(parser.evalNode("/configuration"));

XMLConfigBuilder.class

/**
 * 解析mybatis-config.xml
 * @return configuration
 */
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析configuration節點<configuration></configuration>
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
/**
 * 解析configuration下子節點的屬性
 * @param root configuration節點
 */
private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    // 1> 解析properties節點資訊
    propertiesElement(root.evalNode("properties"));
    //2> 解析settings節點資訊
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //2.1> 指定 VFS 的實現
    loadCustomVfs(settings);
    //2.2> 載入setting logImpl配置, 指定MyBatis 所用日誌的具體實現
    loadCustomLogImpl(settings);
    //2.3> 解析並註冊別名
    typeAliasesElement(root.evalNode("typeAliases"));
    //2.4 解析並載入外掛到攔截器
    pluginElement(root.evalNode("plugins"));
    //2.5 解析並載入物件工廠objectFactory
    objectFactoryElement(root.evalNode("objectFactory"));
    //2.6 解析並載入objectWrapperFactory
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    //2.7 解析並載入reflectorFactory
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    //2.8設定settings屬性值
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    //2.9解析environments節點資訊,並設定environment屬性
    environmentsElement(root.evalNode("environments"));
    //2.10解析databaseIdProvider節點(資料庫廠商資訊)
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    //2.11解析typeHandlers節點
    typeHandlerElement(root.evalNode("typeHandlers"));
    //2.12 解析mappers節點
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

4.3 程式碼過程解析

4.3.1 解析properties節點資訊
<properties resource="config.properties">
    <property name="username" value="cmj"/>
</properties>

propertiesElement(root.evalNode("properties"));
 解析<properties>的整體步驟如下:

  • 解析各個子節點獲取name和value屬性值,存入Properties物件中
  • 獲取節點的resource和url屬性值,不能兩個同時有值,否則報錯
  • 從resource或url的輸入流中讀取配置資訊,存入Properties物件中
  • 獲取configuration物件中的Properties屬性,存入Properties物件中
  • XPathParser物件和configuration物件設定最新的Properties屬性
  • 注:由於解析<properties>的時候是先解析其子節點<property>中的屬性,然後再讀取resource或者url中的屬性,所以這回導致同名屬性覆蓋的問題,resource或url中的屬性會覆<property>中的屬性。
/**
* 解析properties節點
* Properties 是一個Hashtable
* @param context propertise節點xnode
*/
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    //1.獲取propertises下propertise屬性
    Properties defaults = context.getChildrenAsProperties();
    //2> 獲取resource屬性
    String resource = context.getStringAttribute("resource");
    //3> 獲取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) {
      //4> resource存在,則讀取resource檔案中的配置
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      //5> url存在,則獲取url檔案中的配置
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    //6> 獲取configuration中的Properties
    Properties vars = configuration.getVariables();
    if (vars != null) {
      //6.1> Properties的defaults加入從configuration獲取的properties
      defaults.putAll(vars);
    }
    //7> XPathParser 設定Properties屬性
    parser.setVariables(defaults);
    //8> configuration設定Properties屬性
    configuration.setVariables(defaults);
  }
}

1.1獲取<properties>子節點的屬性值

/**
* 獲取子節點的屬性值 name 和 value
*/
public Properties getChildrenAsProperties() {
  Properties properties = new Properties();
  for (XNode child : getChildren()) {
    String name = child.getStringAttribute("name");
    String value = child.getStringAttribute("value");
    if (name != null && value != null) {
      properties.setProperty(name, value);
    }
  }
  return properties;
}

2.1獲取resource或url屬性值: context.getStringAttribute("resource");

/**
 * 獲取指定屬性的值
 * @param name 屬性名稱
 * @return 屬性值
*/
public String getStringAttribute(String name) {
  return getStringAttribute(name, null);
}

/**
 * 獲取指定屬性的值
 * @param name 屬性名稱
 * @param def 預設值
 * @return 屬性值
*/
public String getStringAttribute(String name, String def) {
  String value = attributes.getProperty(name);
  if (value == null) {
    return def;
  } else {
    return value;
  }
}

4.1讀取resource檔案中的配置資訊:Resources.getResourceAsProperties(resource)

/**
 * Returns a resource on the classpath as a Properties object
 *
 * @param resource The resource to find
 * @return The resource
 * @throws java.io.IOException If the resource cannot be found or read
 */
public static Properties getResourceAsProperties(String resource) throws IOException {
  Properties props = new Properties();
  //1> 獲取resource輸入流
  try (InputStream in = getResourceAsStream(resource)) {
    //2> 載入輸入流中的配置
    props.load(in);
  }
  return props;
}

5.1讀取url檔案的配置資訊:Resources.getUrlAsProperties(url)

/**
 * Gets a URL as a Properties object
 *
 * @param urlString - the URL to get
 * @return A Properties object with the data from the URL
 * @throws java.io.IOException If the resource cannot be found or read
 */
public static Properties getUrlAsProperties(String urlString) throws IOException {
  Properties props = new Properties();
  //1> 通過URLConnection,獲取url輸入流
  try (InputStream in = getUrlAsStream(urlString)) {
    //2> 載入輸入流中的配置
    props.load(in);
  }
  return props;
}
4.3.2 解析settings節點資訊

settings中的配置比較多,具體可以看官網

<settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
    <setting name="defaultFetchSize" value="100"/>
    <setting name="safeRowBoundsEnabled" value="false"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="localCacheScope" value="SESSION"/>
    <setting name="jdbcTypeForNull" value="OTHER"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

settingsAsProperties(root.evalNode("settings"));
 解析settings的步驟分為如下幾步:

  • 1.將settings下的所有子節點獲取屬性值,並存入Properties物件中
  • 2.loadCustomVfs(settings);獲取vfsImpl配置,載入指定的vfs類
  • 3.loadCustomLogImpl(settings);獲取logImpl配置,指定MyBatis 所用日誌的具體實現
  • 4.根據Properties配置資訊,設定configuration對應的setting對應的屬性值

第一步:獲取settings下的所有子節點屬性值,這一步比較複雜,獲取<settrings>各個子節點的屬性,然後接下來通過反射去校驗Configuration類中是否有相應的配置屬性。

/**
 * 讀取settings的子節點屬性
 * @param context settings節點
 * @return Properties
 */
private Properties settingsAsProperties(XNode context) {
  if (context == null) {
    return new Properties();
  }
  //1> 獲取<settings>下子節點<setting>的屬性值
  Properties props = context.getChildrenAsProperties();
  //2> 獲取Configuration的元資訊
  MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  // Check that all settings are known to the configuration class
  //3> 檢查<setting>屬性配置在Configuration中是否存在相應的setter方法
  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;
}

1.獲取子節點下的屬性資訊context.getChildrenAsProperties()

/**
 * 獲取子節點的Properties屬性 name 和 value
 */
public Properties getChildrenAsProperties() {
  Properties properties = new Properties();
  for (XNode child : getChildren()) {
    String name = child.getStringAttribute("name");
    String value = child.getStringAttribute("value");
    if (name != null && value != null) {
      properties.setProperty(name, value);
    }
  }
  return properties;
}

1.獲取Configuration的元資訊: MetaClass.forClass(Configuration.class, localReflectorFactory)
MetaClass類中含有ReflectorFactory和Reflector,這是mybatis的反射核心類,用於獲取目標類的各種屬性,ReflectorFactory的實現類是DefaultReflectorFactory,這裡也涉及到工廠模式。MetaClass類的建構函式是私有的,所以不能通過建構函式建立,需要通過forClass方法去建立。

MetaClass.class

public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
  return new MetaClass(type, reflectorFactory);
}

private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
  this.reflectorFactory = reflectorFactory;
  //根據型別建立 Reflector
  this.reflector = reflectorFactory.findForClass(type);
}

DefaultReflectorFactory.class

@Override
public Reflector findForClass(Class<?> type) {
  if (classCacheEnabled) {
    // synchronized (type) removed see issue #461
    return reflectorMap.computeIfAbsent(type, Reflector::new);
  } else {
    return new Reflector(type);
  }
}

上述程式碼建立了Reflector 實體類。
在原始碼上Reflector的註釋是這樣的,很清晰知道它有什麼用了

This class represents a cached set of class definition information that(這個類用於存放類定義的資訊)
allows for easy mapping between property names and getter/setter methods.(能夠方便對映屬性名稱和getter和setter方法)

Reflector.class
Reflector 類建立解析,建立Reflector 物件時會對目標類的各個變數和getter\setter方法進行解析,並分為以下幾個步驟:

  1. 解析目標類的無參建構函式,並賦值到defaultConstructor成員變數中
  2. 解析getter方法,並將解析結果存入getMethods和getTypes中
  3. 解析setter方法,並將解析結果存入setMethods和setTypes中
  4. 解析欄位屬性
/**
 * 初始化Reflector
 * 獲取目標類的資訊
 * @param clazz 目標類
 */
public Reflector(Class<?> clazz) {
  type = clazz;
  //1> 獲取類的建構函式並賦值給defaultConstructor
  addDefaultConstructor(clazz);
  //2> 解析getter方法,並將解析結果存入getMethods和getTypes中
  addGetMethods(clazz);
  //3> 解析setter方法,並將解析結果存入setMethods和setTypes中
  addSetMethods(clazz);
  //4> 解析欄位屬性,將欄位屬性資訊存入getMethods,setMethods,getTypes,setTypes中
  addFields(clazz);
  //getMethods中key集合(可讀屬性名稱集合)
  readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
  //setMethods中key集合(可寫屬性名稱集合)
  writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
  for (String propName : readablePropertyNames) {
    caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
  }
  for (String propName : writeablePropertyNames) {
    caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
  }
}

Reflector:建構函式解析,解析目標類的無參建構函式

private void addDefaultConstructor(Class<?> clazz) {
  Constructor<?>[] consts = clazz.getDeclaredConstructors();
  for (Constructor<?> constructor : consts) {
    if (constructor.getParameterTypes().length == 0) {
        this.defaultConstructor = constructor;
    }
  }
}

Reflector:getter方法解析

getter方法解析主要分為三步

  1. 獲取get / is 開頭的無參方法
  2. 根據規則解決getter方法衝突(具體看下面程式碼解析)
 /**
  * 新增目標類的getter方法
  * @param cls 目標類
  */
private void addGetMethods(Class<?> cls) {
 Map<String, List<Method>> conflictingGetters = new HashMap<>();
 Method[] methods = getClassMethods(cls);
 for (Method method : methods) {
     //排除有引數的方法
   if (method.getParameterTypes().length > 0) {
     continue;
   }
   String name = method.getName();
   //isXXX()和getXXX()方法都獲取
   if ((name.startsWith("get") && name.length() > 3)
       || (name.startsWith("is") && name.length() > 2)) {
       //獲取方法名稱,並首位轉成小寫字母。如getAge()或isAge()會獲取得到age
     name = PropertyNamer.methodToProperty(name);
     //將方法存入Map<String, List<Method>> conflictingGetters,以待用於解決衝突
     addMethodConflict(conflictingGetters, name, method);
   }
 }
 //解決衝突的方法,並給setMethods和setTypes賦值
 resolveGetterConflicts(conflictingGetters);
}

解決getter方法衝突: resolveGetterConflicts(conflictingGetters),有下面的規則:
1.如果兩個getter方法返回型別一樣且不是boolean返回型別,則丟擲異常
2.如果兩個getter方法返回型別一樣且是返回型別未boolean的isXXX()方法,則選取該方法
3.如果兩個返回型別不一樣,選取返回型別是子類的getter方法

 /**
   * 解決Getter衝突,如:isAge()和getAge()便是衝突方法
   * 只有List<Method> > 1 時才需要解決衝突
   * @param conflictingGetters 方法集合
   */
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
  for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
    Method winner = null;
    String propName = entry.getKey();
    for (Method candidate : entry.getValue()) {
      if (winner == null) {
        winner = candidate;
        continue;
      }
      Class<?> winnerType = winner.getReturnType();
      Class<?> candidateType = candidate.getReturnType();
      //如果兩個方法返回型別一致
      if (candidateType.equals(winnerType)) {
          //如果兩個方法返回型別一致,且返回型別都不是boolean型別則丟擲異常
        if (!boolean.class.equals(candidateType)) {
          throw new ReflectionException(
              "Illegal overloaded getter method with ambiguous type for property "
                  + propName + " in class " + winner.getDeclaringClass()
                  + ". This breaks the JavaBeans specification and can cause unpredictable results.");
          //如果返回型別為boolean且是isXXX的方法則candidate勝出
        } else if (candidate.getName().startsWith("is")) {
          winner = candidate;
        }
        //如果winnerType是candidateType,則選取winner
      } else if (candidateType.isAssignableFrom(winnerType)) {
        //如果candidateType是winnerType的子類,則選取candidate
      } else if (winnerType.isAssignableFrom(candidateType)) {
        winner = candidate;
      } else {
        throw new ReflectionException(
            "Illegal overloaded getter method with ambiguous type for property "
                + propName + " in class " + winner.getDeclaringClass()
                + ". This breaks the JavaBeans specification and can cause unpredictable results.");
      }
    }
    //將篩選出的方法新增到getMethods並將其返回值新增到getTypes
    addGetMethod(propName, winner);
  }
}

Reflector:setter方法解析

setter方法解析主要分為三步

  1. 獲取set 開頭且只有一個入參的方法
  2. 根據規則解決setter方法衝突(具體看下面程式碼解析)
/**
 * 新增setter方法到setMethods變數中
 * @param cls 目標類
 */
private void addSetMethods(Class<?> cls) {
  Map<String, List<Method>> conflictingSetters = new HashMap<>();
  Method[] methods = getClassMethods(cls);
  for (Method method : methods) {
    String name = method.getName();
    //1> 獲取只有一個引數的setXXX()方法,等到方法名XXX
    if (name.startsWith("set") && name.length() > 3) {
      if (method.getParameterTypes().length == 1) {
        name = PropertyNamer.methodToProperty(name);
        //2> 新增放到到衝突列表:conflictingSetters,待進行衝突處理
        addMethodConflict(conflictingSetters, name, method);
      }
    }
  }
  //3> 解決setter方法衝突
  resolveSetterConflicts(conflictingSetters);
}

解決setter方法衝突:resolveSetterConflicts(conflictingSetters);
1.如果setter方法入參型別與對應的getter方法返回型別一致,則選取
2.如果存在兩個setter方法,判斷引數型別,取引數型別是子類的方法,若引數型別不是父子類關係,則丟擲異常

  /**
   * setter方法衝突篩選,將最終篩選出來的存入setMethods和setTypes變數中
   * @param conflictingSetters setter方法集合
   */
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
  for (String propName : conflictingSetters.keySet()) {
    List<Method> setters = conflictingSetters.get(propName);
    Class<?> getterType = getTypes.get(propName);
    Method match = null;
    ReflectionException exception = null;
    for (Method setter : setters) {
      //1> setter方法的引數型別與getter返回值型別一致,則當前setter方法為目標方法
      Class<?> paramType = setter.getParameterTypes()[0];
      if (paramType.equals(getterType)) {
        // should be the best match
        match = setter;
        break;
      }
      //2> 如果存在兩個setter方法,判斷引數型別,取引數型別是子類的方法,若引數型別不是父子類關係,則丟擲異常
      if (exception == null) {
        try {
          match = pickBetterSetter(match, setter, propName);
        } catch (ReflectionException e) {
          // there could still be the 'best match'
          match = null;
          exception = e;
        }
      }
    }
    if (match == null) {
      throw exception;
    } else {
    //3> 排除衝突後的setter方法存入setMethods
      addSetMethod(propName, match);
    }
  }
}

pickBetterSetter(match, setter, propName) 方法用於比較兩個setter方法,篩選入參是子類的setter方法

/**
 * 引數型別比較,返回子類
 * 如果型別不是父子類關係,直接報錯
 * @param setter1 方法1
 * @param setter2 方法2
 * @param property property
 * @return 返回是子類的方法
 */
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
  if (setter1 == null) {
    return setter2;
  }
  Class<?> paramType1 = setter1.getParameterTypes()[0];
  Class<?> paramType2 = setter2.getParameterTypes()[0];
  //paramType2是paramType1的子類
  if (paramType1.isAssignableFrom(paramType2)) {
    return setter2;
  //paramType1是paramType2的子類
  } else if (paramType2.isAssignableFrom(paramType1)) {
    return setter1;
  }
  throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
      + setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
      + paramType2.getName() + "'.");
}
4.3.3 解析typeAliases節點資訊

用於定義類的別名,可以在xml中的resultType中直接使用別名。

 <typeAliases>
     <package name="com.chen.mybatis.demo2.pojo"/>
     <typeAlias type="com.chen.mybatis.demo2.pojo.BlogTypealiase" alias="blogTypealiase"/>
 </typeAliases>

typeAliasesElement(root.evalNode("typeAliases"));
解析別名,並對別名進行註冊的步驟如下

1.解析package節點,將包下所有的類註冊別名。
2.解析typeAlias節點,根據type和alias資訊註冊別名。

方法typeAliasesElement(XNode parent)解析,對package和typeAlias節點資訊分別進行解析並註冊別名,別名若存在則丟擲異常
別名的規則:

1.獲取package節點的包下的所有類進行註冊
2.獲取typeAlias節點type和alias資訊註冊別名

/**
 * 解析別名typeAliases
 */
private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //1> 如果是package節點,則將包下的所有類註冊到別名中
      if ("package".equals(child.getName())) {
        String typeAliasPackage = child.getStringAttribute("name");
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
      //2> 如果是typeAlias節點,則將每一個typeAlias節點的資訊註冊到別名中
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        try {
          //2.1> 根據typeAlias節點的type和alias資訊註冊別名
          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);
        }
      }
    }
  }
}
  • 4.3.3.1 註冊package節點下的包下的所有類的別名
    configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage)
public void registerAliases(String packageName){
  registerAliases(packageName, Object.class);
}

/**
 * 註冊包下類的別名
 * @param packageName 報名
 * @param superType 父類
 */
public void registerAliases(String packageName, Class<?> superType){
  //獲取包下是Object.class的子類的類
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  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);
    }
  }
}

registerAlias(Class> type)和registerAlias(String alias, Class> value)是註冊別名的主要方法。

第一步獲取類名作為別名
第二步如果類中存在註解@Alias則獲取註解的value作為別名
第三步將別名轉換為小寫後再對該類進行別名註冊

/**
 * 註冊別名
 * 獲取類中@Alias註解作為別名,如果不存在則獲取類名
 * @param type 目標類
 */
public void registerAlias(Class<?> type) {
  //1.獲取類名
  String alias = type.getSimpleName();
  //2.獲取目標類中的@Alias註解資訊,value值
  Alias aliasAnnotation = type.getAnnotation(Alias.class);
  if (aliasAnnotation != null) {
    alias = aliasAnnotation.value();
  }
  //3.註冊別名
  registerAlias(alias, type);
}

/**
 * 註冊別名
 * @param alias 別名
 * @param value 目標類
 */
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);
}
  • 4.3.3.2 根據typeAlias節點下type和alias資訊註冊別名
    這部分程式碼跟包別名註冊差不多,,主要還是通過registerAlias(Class> type)和registerAlias(String alias, Class> value)這兩個方法進行別名註冊。
 String alias = child.getStringAttribute("alias");
 String type = child.getStringAttribute("type");
 try {
   //2.1> 根據typeAlias節點的type和alias資訊註冊別名
   Class<?> clazz = Resources.classForName(type);
   if (alias == null) {
     typeAliasRegistry.registerAlias(clazz);
   } else {
     typeAliasRegistry.registerAlias(alias, clazz);
   }
  • 4.3 解析並載入plugins外掛到攔截器中
 <plugins>
     <plugin interceptor="com.chen.mybatis.demo2.plugins.ExamplePlugin">
         <property name="someProperty" value="101"/>
     </plugin>
 </plugins>

pluginElement(root.evalNode("plugins"))載入外掛,其中程式碼比較清晰就直接放程式碼了

/**
 *載入plugins外掛到攔截器中
 */
private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //1> 獲取攔截器的類全路徑
      String interceptor = child.getStringAttribute("interceptor");
      //2> 獲取plugin節點下property節點的資訊
      Properties properties = child.getChildrenAsProperties();
      //3> 建立攔截器例項
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      //4> 攔截器設定properties熟悉
      interceptorInstance.setProperties(properties);
      //5> configuration新增攔截器
      configuration.addInterceptor(interceptorInstance);
    }
  }
}
4.3.4 解析並載入物件工廠objectFactory
<objectFactory type="com.chen.mybatis.demo2.objectFactory.ExampleObjectFactory">
    <property name="someProperty" value="100"/>
</objectFactory>

objectFactoryElement(root.evalNode("objectFactory"));設定objectFactory物件。

/**
 * 解析並載入物件工廠objectFactory
 */
private void objectFactoryElement(XNode context) throws Exception {
  if (context != null) {
    //1> 獲取type屬性的值(類的全路徑)
    String type = context.getStringAttribute("type");
    //2> 獲取property節點資訊name和value值
    Properties properties = context.getChildrenAsProperties();
    //3> 構建ObjectFactory物件
    ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
    //4> ObjectFactory設定property屬性
    factory.setProperties(properties);
    //5> configuration設定ObjectFactory
    configuration.setObjectFactory(factory);
  }
}
4.3.5 解析並載入物件加工工廠ObjectWrapperFactory
<objectWrapperFactory type="com.chen.mybatis.demo2.objectFactory.ExampleObjectWrapperFactory"/>

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));程式碼就如下了,比較簡單

/**
 * 解析並載入objectWrapperFactory
 */
private void objectWrapperFactoryElement(XNode context) throws Exception {
  if (context != null) {
    //1> 獲取type屬性的值(類的全路徑)
    String type = context.getStringAttribute("type");
    //2> 構建ObjectWrapperFactory物件
    ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
    //3> configuration設定ObjectWrapperFactory
    configuration.setObjectWrapperFactory(factory);
  }
}
4.3.6 解析並載入反射工廠ReflectorFactory
<reflectorFactory type="com.chen.mybatis.demo2.objectFactory.ExampleReflectorFactory"/>

reflectorFactoryElement(root.evalNode("reflectorFactory"));

/**
 * 解析並載入reflectorFactory
 */
private void reflectorFactoryElement(XNode context) throws Exception {
  if (context != null) {
    //1> 獲取type屬性的值(類的全路徑)
     String type = context.getStringAttribute("type");
    //2> 構建ReflectorFactory物件
     ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
    //3> configuration設定ReflectorFactory
     configuration.setReflectorFactory(factory);
  }
}
4.3.7 設定settings屬性值

將之前解析得到的setting屬性資訊設定到configuration中,其中有一些是會有自己的預設值的。
settingsElement(settings);

  /**
   * 設定setting屬性值
   * @param props Properties物件
   */
  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.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")));
  }
4.3.8 解析environments節點資訊,並設定environment屬性
事務管理器和資料來源都在environments節點下進行配置。transactionManager節點是事務管理器,dataSource節點是資料來源。
 <environments default="development">
     <environment id="development">
         <transactionManager type="JDBC"/>
         <dataSource type="POOLED">
             <property name="driver" value="${driver}"/>
             <property name="url" value="${url}"/>
             <property name="username" value="${username}"/>
             <property name="password" value="${password}"/>
         </dataSource>
     </environment>
 </environments>

environmentsElement(root.evalNode("environments"));

/**
 * 解析environments節點資訊
 */
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      //1>獲取environments節點default屬性值
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      //2>獲取environment節點id值
      String id = child.getStringAttribute("id");
      //3> 判斷id是否為environments節點的default值
      if (isSpecifiedEnvironment(id)) {
        //4>解析transactionManager節點,建立TransactionFactory物件
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //5>解析dataSource節點,建立DataSourceFactory物件
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        //6.建立Environment.Builder物件,並設定transactionFactory和dataSource
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        //7.configuration設定environment屬性
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}
4.3.9 解析databaseIdProvider節點(資料庫廠商資訊)

databaseIdProvider的配置一般如下

 <databaseIdProvider type="DB_VENDOR">
     <property name="MySQL" value="mysql"/>
     <property name="Oracle" value="oracle"/>
 </databaseIdProvider>

databaseIdProviderElement(root.evalNode("databaseIdProvider"));
配置資料庫廠商資訊,也就是設定DatabaseId值。mybatis會通過資料來源獲取到資料來源中資料庫名稱,再根據該名稱跟所配置的資訊進行對比,拿去與資料來源名稱一致的property 的value值作為DatabaseId,設定到configuration中。

/**
 * 解析databaseIdProvider節點
 */
private void databaseIdProviderElement(XNode context) throws Exception {
  DatabaseIdProvider databaseIdProvider = null;
  if (context != null) {
    //1> 獲取type屬性值
    String type = context.getStringAttribute("type");
    // awful patch to keep backward compatibility
    if ("VENDOR".equals(type)) {
        type = "DB_VENDOR";
    }
    //2> 獲取databaseIdProvider節點下的property節點資訊,並建立DatabaseIdProvider物件
    Properties properties = context.getChildrenAsProperties();
    databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
    databaseIdProvider.setProperties(properties);
  }
  //3> 獲取databaseId,並設定configuration的databaseId
  Environment environment = configuration.getEnvironment();
  if (environment != null && databaseIdProvider != null) {
    String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
    configuration.setDatabaseId(databaseId);
  }
}

先看看第二步:DatabaseIdProvider databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();

  • 當type = “DB_VENDOR” 時,建立的DatabaseIdProvider類實現類實際上是:VendorDatabaseIdProvider類
  • 為什麼?因為Configuration建立的時候一樣註冊了別名。
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

再來看第三步,獲取databaseId,並設定configuration的databaseId的程式碼實現。主要是這段程式碼
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
會先通過資料來源獲取資料的產品名稱資訊,再跟property的name屬性相比較,如果是當前資料庫的產品名稱則拿去其value值作為DatabaseId。

/**
 * 獲取資料庫名稱
 * @param dataSource 資料來源
 */
@Override
public String getDatabaseId(DataSource dataSource) {
    if (dataSource == null) {
        throw new NullPointerException("dataSource cannot be null");
    }
    try {
        return getDatabaseName(dataSource);
    } catch (Exception e) {
        LogHolder.log.error("Could not get a databaseId from dataSource", e);
    }
    return null;
}
/**
 * 獲取資料庫名稱
 * @param dataSource 資料來源
 * @return 資料庫名稱
 */
private String getDatabaseName(DataSource dataSource) throws SQLException {
    //1> 獲取資料庫產品名稱如oracle、mysql
    String productName = getDatabaseProductName(dataSource);
    //2> 如果資料來源中資料庫產品名稱包含databaseIdProvider節點下的property節點name屬性值,則返回對應的value值作為資料庫名稱
    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;
}

/**
 * 獲取資料庫產品名稱如oracle、mysql
 */
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
            }
        }
    }
}
4.3.10 解析typeHandlers節點

typeHandlers中關於TypeHandlerRegistry類走register()各種過載方法互相呼叫,一時間有點頭暈。
從入口方法中有兩種處理,一種是自動對映package下的typehandler,另一種是解析單個節點的typeHandler。

/**
 * 解析typeHandlers節點,並對typehandler進行註冊
 */
private void typeHandlerElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //1> 從指定包中註冊TypeHandler
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
        //2>解析typeHandler節點,獲取javaType,jdbcType,handler屬性值
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
        String handlerTypeName = child.getStringAttribute("handler");
        //3> 獲取javaTypeName為別名對應的類,如不存在,則返回javaTypeName指定的類
        Class<?> javaTypeClass = resolveClass(javaTypeName);
        //4> 獲取jdbcTypeName對應的JdbcType
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        //5> 獲取handlerTypeName為別名對應的類,如不存在,則返回handlerTypeName指定的類
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
        //6> 註冊TypeHandler
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

TypeHandlerRegistry類中register過載方法的呼叫關係圖,比較清晰的看到register過載方法的呼叫

register(String packageName)方法
該方法用於自動掃描包中的typehandler,並進行註冊

/**
 * 註冊TypeHandler,自動掃描型別處理器
 * @param packageName 包路徑
 */
public void register(String packageName) {
  //1> 獲取包下的類
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
  Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
  //2> 註冊TypeHandler,非內部類,介面,抽象類才能註冊
  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);
    }
  }
}

register(Class<?> typeHandlerClass)方法
該方法主要用於判斷typehandler中javaType是否存在,從而呼叫不同的過載方法

 /**
  * 存在@MappedTypes,且有值,則存在javaType,呼叫register(Class<?> javaTypeClass, Class<?> typeHandlerClass)過載方法
  * 不存在@MappedTypes或者沒有值,則呼叫register(TypeHandler<T> typeHandler)過載方法
  */
public void register(Class<?> typeHandlerClass) {
 boolean mappedTypeFound = false;
 //1> 獲取@MappedTypes註解
 MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
 if (mappedTypes != null) {
     //2> 遍歷@MappedTypes註解中value值
   for (Class<?> javaTypeClass : mappedTypes.value()) {
       //3> 呼叫register過載方法進行註冊
     register(javaTypeClass, typeHandlerClass);
     mappedTypeFound = true;
   }
 }
 if (!mappedTypeFound) {
   register(getInstance(null, typeHandlerClass));
 }
}

register(TypeHandler<T> typeHandler)方法

/**
* 只有typeHandler引數的register過載方法
*
*/
public <T> void register(TypeHandler<T> typeHandler) {
  boolean mappedTypeFound = false;
  //1> 獲取@MappedTypes註解,存在javaType值,則呼叫register(Type javaType, TypeHandler<? extends T> typeHandler)方法註冊typehandler
  MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
  if (mappedTypes != null) {
    for (Class<?> handledType : mappedTypes.value()) {
      //呼叫過載方法register(Type javaType, TypeHandler<? extends T> typeHandler)
      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(Type javaType, TypeHandler<? extends 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 javaType, TypeHandler<? extends T> typeHandler)方法
判斷是否存在註解@MappedJdbcTypes(JdbcType)是否存在,再將jdbcType作為入參,呼叫register過載方法

  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
 //1> 獲取typahandler類中@MappedJdbcTypes註解value值
 MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
 if (mappedJdbcTypes != null) {
   //2> 遍歷MappedJdbcTypes的value值,進行遍歷註冊TypeHandler
   for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
     register(javaType, handledJdbcType, typeHandler);
   }
   //3> 如果MappedJdbcTypes註解中includeNullJdbcType=true,則註冊jdbcType=null的TypeHandler
   if (mappedJdbcTypes.includeNullJdbcType()) {
     register(javaType, null, typeHandler);
   }
 } else {
   register(javaType, null, typeHandler);
 }
}

register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)最終呼叫的方法
實現將typehandler儲存到對應的稱員變數中(map)

/**
 * 註冊TypeHandler
 * @param javaType
 * @param jdbcType
 * @param handler
 */
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
  if (javaType != null) {
    //1> 儲存以javaType為key的 Map<JdbcType, TypeHandler<?>>到TYPE_HANDLER_MAP變數中
    Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
    if (map == null || map == NULL_TYPE_HANDLER_MAP) {
      map = new HashMap<>();
      TYPE_HANDLER_MAP.put(javaType, map);
    }
    map.put(jdbcType, handler);
  }
  //2> 新增TypeHandler到ALL_TYPE_HANDLERS_MAP
  ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
4.3.11 解析mappers節點

解析mappers節點這個放到之後的章節再進行描述。

附:用於學習的mybatis原始碼

https://github.com/569844962/mybatis-3.git

相關文章