MyBatis 學(xué)習(xí)筆記(四)---源碼分析篇---配置文件的解析過程(一)

概述

前幾篇我們介紹了MyBatis的一些基本特性,對MyBatis有了個(gè)初步了解。接下來,我們將著手來分析一下MyBatis的源碼,從源碼層面復(fù)盤MyBatis的執(zhí)行流程。

思維導(dǎo)圖概括

在這里插入圖片描述

配置文件解析過程分析

有了上述思維導(dǎo)圖,我們對配置文件文件的解析過程就有了一個(gè)大概的認(rèn)識,下面我們就來具體分析下解析過程。

配置文件解析入口

首先,我們來看看調(diào)用MyBatis的示例代碼

String resource = "chapter1/mybatis-cfg.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

從上述示例代碼中我們可以很清晰的看出,初始化過程是首先通過Resources 解析配置文件得到文件流。然后,將文件流傳給SqlSessionFactoryBuilder的build方法,并最終得到sqlSessionFactory。
那么我們MyBatis的初始化入口就是SqlSessionFactoryBuilder的build 方法。

//* SqlSessionFactoryBuilder類
//以下3個(gè)方法都是調(diào)用下面第8種方法
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  //第8種方法和第4種方法差不多,Reader換成了InputStream
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }
  //最后一個(gè)build方法使用了一個(gè)Configuration作為參數(shù),并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

從上述源碼,我們可以知道build 構(gòu)建SqlSessionFactory 分為兩步,首先 實(shí)例化一個(gè)XMLConfigBuilder,然后,調(diào)用XMLConfigBuilder的parse方法得到Configuration對象,最后將Configuration對象作為參數(shù)實(shí)例化一個(gè)DefaultSqlSessionFactory 即SqlSessionFactory對象。
接著往下看,下面我們來看看XMLConfigBuilder類。首先是實(shí)例化XMLConfigBuilder的過程。

//* XMLConfigBuilder
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  
  //上面6個(gè)構(gòu)造函數(shù)最后都合流到這個(gè)函數(shù),傳入XPathParser
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //首先調(diào)用父類初始化Configuration
    super(new Configuration());
    //錯(cuò)誤上下文設(shè)置成SQL Mapper Configuration(XML文件配置),以便后面出錯(cuò)了報(bào)錯(cuò)用吧
    ErrorContext.instance().resource("SQL Mapper Configuration");
    //將Properties全部設(shè)置到Configuration里面去
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
//* XPathParser
  public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }
   private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
		//這個(gè)是DOM解析方式
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);

		//名稱空間
      factory.setNamespaceAware(false);
		//忽略注釋
      factory.setIgnoringComments(true);
		//忽略空白
      factory.setIgnoringElementContentWhitespace(false);
		//把 CDATA 節(jié)點(diǎn)轉(zhuǎn)換為 Text 節(jié)點(diǎn)
      factory.setCoalescing(false);
		//擴(kuò)展實(shí)體引用
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
		//需要注意的就是定義了EntityResolver(XMLMapperEntityResolver),這樣不用聯(lián)網(wǎng)去獲取DTD,
		//將DTD放在org\apache\ibatis\builder\xml\mybatis-3-config.dtd,來達(dá)到驗(yàn)證xml合法性的目的
      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 {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

從上述源碼中,我們可以看出在XMLConfigBuilder的實(shí)例化過程包括兩個(gè)過程,1. 創(chuàng)建XPathParser的實(shí)例并初始化;2.創(chuàng)建Configuration的實(shí)例對象,然后將XPathParser的實(shí)例設(shè)置到XMLConfigBuilder中,而XPathParser 初始化主要做了兩件事,初始化DocumentBuilder對象,并通過調(diào)用DocumentBuilder對象的parse方法得到Document對象,我們配置文件的配置就全部都轉(zhuǎn)移到了Document對象中。我們下面通過調(diào)試看看Document 對象中的內(nèi)容,測試用例是MyBatis 自身的單元測試XPathParserTest
測試的xml

<!--
nodelet_test.xml
-->
<employee id="${id_var}">
  <blah something="that"/>
  <first_name>Jim</first_name>
  <last_name>Smith</last_name>
  <birth_date>
    <year>1970</year>
    <month>6</month>
    <day>15</day>
  </birth_date>
  <height units="ft">5.8</height>
  <weight units="lbs">200</weight>
  <active>true</active>
</employee>

測試用例:

//* XPathParserTest
  @Test
  public void shouldTestXPathParserMethods() throws Exception {
    String resource = "resources/nodelet_test.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    XPathParser parser = new XPathParser(inputStream, false, null, null);
    assertEquals((Long)1970l, parser.evalLong("/employee/birth_date/year"));
    assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
    assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
    assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
    assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
    assertEquals("${id_var}", parser.evalString("/employee/@id"));
    assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
    assertEquals("<id>${id_var}</id>", parser.evalNode("/employee/@id").toString().trim());
    assertEquals(7, parser.evalNodes("/employee/*").size());
    XNode node = parser.evalNode("/employee/height");
    assertEquals("employee/height", node.getPath());
    assertEquals("employee[${id_var}]_height", node.getValueBasedIdentifier());
  }

調(diào)試結(jié)果:
在這里插入圖片描述













在這里插入圖片描述
















介紹完XMLConfigBuilder的初始化過程之后,接著我們來看看XMLConfigBuilder中的parse()方法,由前面其初始化過程我們可以得知我們的配置信息已經(jīng)保存到了XMLConfigBuilder的XPathParser對象的Document中了。解析來其實(shí)就是將XPathParser中的信息轉(zhuǎn)移到Configuration對象中,不多說了,看看源碼。

//* XMLConfigBuilder
  //解析配置
  public Configuration parse() {
    //如果已經(jīng)解析過了,報(bào)錯(cuò)
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;    
    //根節(jié)點(diǎn)是configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
    //解析配置
  private void parseConfiguration(XNode root) {
    try {
      //分步驟解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.類型別名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.對象工廠
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.對象包裝工廠
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.設(shè)置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.環(huán)境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.類型處理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

至此,一個(gè)MyBatis的解析過程就出來了,每個(gè)配置的解析邏輯封裝在相應(yīng)的方法中,接下來將重點(diǎn)介紹一些常用的配置,例如properties,settings,environments,typeAliases, typeHandler, mappers。閑話少敘,接下來我們首先來分析下properties的解析過程

解析properties配置

首先我們來看看一個(gè)普通的properties配置。

  <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
 </properties>
  • 1
  • 2
  • 3
  • 4
//* XMLConfigBuilder
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //如果在這些地方,屬性多于一個(gè)的話,MyBatis 按照如下的順序加載它們:
      //1.在 properties 元素體內(nèi)指定的屬性首先被讀取。
      //2.從類路徑下資源或 properties 元素的 url 屬性中加載的屬性第二被讀取,它會覆蓋已經(jīng)存在的完全一樣的屬性。
      //3.作為方法參數(shù)傳遞的屬性最后被讀取, 它也會覆蓋任一已經(jīng)存在的完全一樣的屬性,這些屬性可能是從 properties 元素體內(nèi)和資源/url 屬性中加載的。
      //傳入方式是調(diào)用構(gòu)造函數(shù)時(shí)傳入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
      //1.XNode.getChildrenAsProperties函數(shù)方便得到孩子所有Properties
      Properties defaults = context.getChildrenAsProperties();
      //2.然后查找resource或者url,加入前面的Properties
      String resource = context.getStringAttribute("resource");
      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) {
        //從文件系統(tǒng)中加載并解析屬性文件
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        //通過url加載并解析屬性文件
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //3.Variables也全部加入Properties
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      //4. 將屬性值設(shè)置到configuration中
      configuration.setVariables(defaults);
    }
  }
  
  /**
   *   //得到孩子,返回Properties,孩子的格式肯定都有name,value屬性
   * @return
   */
  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) {
//        設(shè)置屬性到屬性對象中
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

代碼中注釋比較詳實(shí),代碼結(jié)構(gòu)不太復(fù)雜,讀者們看下就會明白。不過需要特別說明:properties元素的解析順序是:
1. 在Properties 元素體內(nèi)指定的屬性首先被讀取。
2. 在類路徑下資源或properties元素的url 屬性中加載的屬性第二個(gè)被讀取,它會覆蓋完全一樣的屬性
3. 作為方法參數(shù)傳遞的屬性最后被讀取,它也會覆蓋任一已存在的完全一樣的屬性,這些屬性可能是從properties 元素體內(nèi)和資源 /url 屬性中加載的。
//傳入方式是調(diào)用構(gòu)造函數(shù)時(shí)傳入,public XMLConfigBuilder(Reader reader, String environment, Properties props)

解析settings配置

settings 節(jié)點(diǎn)的解析過程

settings相關(guān)配置是MyBatis中非常重要的配置,這些配置用戶調(diào)整MyBatis運(yùn)行時(shí)的行為。settings配置繁多,在對這些配置不熟悉的情況下,保持默認(rèn)的配置即可。詳細(xì)的配置說明可以參考MyBatis官方文檔setting
在這里插入圖片描述
我們先看看一個(gè)settings 的簡單配置

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
</settings>

setting的解析源碼

接下來我們來看看setting的解析源碼。

 //*XMLConfigBuilder
  private void settingsElement(XNode context) throws Exception {
    if (context != null) {
//      獲取settings子節(jié)點(diǎn)中的內(nèi)容
      Properties props = context.getChildrenAsProperties();
      // 創(chuàng)建Configuration 類的"元信息"對象
      MetaClass metaConfig = MetaClass.forClass(Configuration.class);
      for (Object key : props.keySet()) {
        // Check that all settings are known to the configuration class
        //檢查下是否在Configuration類里都有相應(yīng)的setter方法(沒有拼寫錯(cuò)誤)
        if (!metaConfig.hasSetter(String.valueOf(key))) {
          throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
      }

從上述源碼中我們可以總結(jié)出setting 的解析主要分為如下幾個(gè)步驟:

  1. 獲取settings 子節(jié)點(diǎn)中的內(nèi)容,這段代碼在之前已經(jīng)解釋過,再次不在贅述。
  2. 然后就是創(chuàng)建Configuration類的“元信息”對象,在這一部分中出現(xiàn)了一個(gè)陌生的類MetaClass,我們一會在分析。
  3. 接著檢查是否在Configuration類里都有相應(yīng)的setter方法,不存在則拋出異常。
  4. 若通過MetaClass的檢測,則將Properties中的信息設(shè)置到configuration對象中,邏輯結(jié)束
    上述代碼看似簡單,實(shí)際上在第二步創(chuàng)建元信息對象還是蠻復(fù)雜的。接下來我們就來看看MetaClass類
MetaClass類源碼解析
//*MetaClass
public class MetaClass {

    //有一個(gè)反射器
    //可以看到方法基本都是再次委派給這個(gè)Reflector
  private Reflector reflector;

  private MetaClass(Class<?> type) {
//    根據(jù)類型創(chuàng)建Reflector
    this.reflector = Reflector.forClass(type);
  }

  public static MetaClass forClass(Class<?> type) {
//  調(diào)用構(gòu)造器方法
    return new MetaClass(type);
  }

  /**
   * 檢查指定的屬性是否有setter方法。
   * @param name
   * @return
   */
  public boolean hasSetter(String name) {
//    屬性分詞器,用于解析屬性名
    PropertyTokenizer prop = new PropertyTokenizer(name);
//    hasNext返回true,則表明是一個(gè)復(fù)合屬性
    if (prop.hasNext()) {
//      調(diào)用reflector的hasSetter方法
      if (reflector.hasSetter(prop.getName())) {
//        為屬性創(chuàng)建MetaClass
        MetaClass metaProp = metaClassForProperty(prop.getName());
//        再次調(diào)用hasSetter
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      // 非復(fù)合屬性則直接調(diào)用hasSetter一次即可
      return reflector.hasSetter(prop.getName());
    }
  }

  public MetaClass metaClassForProperty(String name) {
    Class<?> propType = reflector.getGetterType(name);
    return MetaClass.forClass(propType);
  }

從源碼我們可以看出MetaClass 的forClass 方法最終委托給了這個(gè)Reflector的forClass方法。而hasSetter 方法中又調(diào)用了reflector的hasSetter方法,那么Reflector類內(nèi)部實(shí)現(xiàn)如何呢?同時(shí)我們還注意到出現(xiàn)了一個(gè)新的類PropertyTokenizer,那么這個(gè)類內(nèi)部實(shí)現(xiàn)如何呢?我們待會再來分析下。首先我們簡單介紹下這幾個(gè)類。

Reflector -----> 反射器,用于解析和存儲目標(biāo)類的元信息
PropertyTokenizer -----> 屬性分詞器,用于解析屬性名。
接下來,我們來看看Reflector的相關(guān)實(shí)現(xiàn)。

Reflector類源碼解析

Reflector 類的源碼較多,在此處我們不做一一分析。我主要從以下三個(gè)方面:

  1. Reflector的構(gòu)造方法和成員變量分析
  2. getter 方法解析過程分析
  3. setter 方法解析過程分析
//* Reflector
  private static boolean classCacheEnabled = true;
  private static final String[] EMPTY_STRING_ARRAY = new String[0];
  //這里用ConcurrentHashMap,多線程支持,作為一個(gè)緩存
  private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<Class<?>, Reflector>();

  private Class<?> type;
  //getter的屬性列表
  private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
  //setter的屬性列表
  private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
  //setter的方法列表
  private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
  //getter的方法列表
  private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
  //setter的類型列表
  private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
  //getter的類型列表
  private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
  //構(gòu)造函數(shù)
  private Constructor<?> defaultConstructor;

  private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();

  /**
   * 得到某個(gè)類的反射器,是靜態(tài)方法,而且要緩存,
   * 又要多線程,所以REFLECTOR_MAP是一個(gè)ConcurrentHashMap
   */
  public static Reflector forClass(Class<?> clazz) {
    if (classCacheEnabled) {
      // synchronized (clazz) removed see issue #461
        //對于每個(gè)類來說,我們假設(shè)它是不會變的,這樣可以考慮將這個(gè)類的信息
      // (構(gòu)造函數(shù),getter,setter,字段)加入緩存,以提高速度
      Reflector cached = REFLECTOR_MAP.get(clazz);
      if (cached == null) {
        cached = new Reflector(clazz);
        REFLECTOR_MAP.put(clazz, cached);
      }
      return cached;
    } else {
      return new Reflector(clazz);
    }
  }

  private Reflector(Class<?> clazz) {
    type = clazz;
    //解析目標(biāo)類的默認(rèn)構(gòu)造方法,并賦值給defaultConstructor變量
    addDefaultConstructor(clazz);
    //解析getter,并將解析結(jié)果放入getMethods中
    addGetMethods(clazz);
    //解析setter方法,并將解析結(jié)果放入setMethods中
    addSetMethods(clazz);
    //解析屬性字段,并將解析結(jié)果添加到setMethods或getMethods中
    addFields(clazz);
//    從getMethods映射中獲取可讀屬性名數(shù)組
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
//    從setMethods 映射中獲取可寫屬性名數(shù)組
    writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
    //將所有屬性名的大寫形式作為鍵,屬性名作為值,存入到caseInsensitivePropertyMap中
    for (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
    for (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
    }
  }
//省略其他方法

如上,Reflector 定義了一個(gè)ConcurrentHashMap 用于緩存每個(gè)類的反射器,以提高速度。我們知道ConcurrentHashMap是一個(gè)線程安全類,所以不存在線程安全問題。同時(shí),其他的集合用于存儲getter,setter 方法的相關(guān)信息。構(gòu)造器里會講元信息里里的構(gòu)造方法,屬性字段,setter方法,getter方法設(shè)置到相應(yīng)的集合中。
接下來,我們來分析下getter方法。

getter方法解析過程分析
//* Reflector
  private void addGetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
//    獲取當(dāng)前類,接口,以及父類中的方法。該方法邏輯不是很復(fù)雜
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
//      getter方法不應(yīng)該有參數(shù),若存在參數(shù),則忽略當(dāng)前方法
      if (method.getParameterTypes().length > 0) {
            continue;
      }
      String name = method.getName();
//      過濾出以get或is開頭的方法
      if (name.startsWith("get") && name.length() > 3) {
        if (method.getParameterTypes().length == 0) {
//          將getXXX方法名轉(zhuǎn)成相應(yīng)的屬性,比如 getName -> name
          name = PropertyNamer.methodToProperty(name);
/*         將沖突的方法添加到conflictingGetters中,考慮這樣一種情況
          getTitle和isTitle兩個(gè)方法經(jīng)過methodToProperty處理,
          均得到 name=title,這會導(dǎo)致沖突
          對于沖突的方法,這里想統(tǒng)一存起來,后續(xù)在解決沖突
          */
          addMethodConflict(conflictingGetters, name, method);
        }
      } else if (name.startsWith("is") && name.length() > 2) {
        if (method.getParameterTypes().length == 0) {
          name = PropertyNamer.methodToProperty(name);
          addMethodConflict(conflictingGetters, name, method);
        }
      }
    }
//    處理getter沖突
    resolveGetterConflicts(conflictingGetters);
  }

如上, addGetMethods 方法的的執(zhí)行流程如下:

  1. 獲取當(dāng)前類,接口,以及父類中的方法
  2. 遍歷上一步獲取的方法數(shù)組,并過濾出以get和is開頭方法
  3. 根據(jù)方法名截取出屬性名
  4. 將沖突的屬性名和方法對象添加到?jīng)_突集合中
  5. 處理getter沖突,篩選出合適的方法。
    我們知道getter截取屬性沖突主要是由于 getXXX() 和isXXX() 兩種類型的方法,截取屬性后會沖突。
    比較核心的知識點(diǎn)就是處理getter 沖突,接下來,我們就來看看相應(yīng)的源碼
//* Reflector

  /**
   * //  添加屬性名和方法對象到?jīng)_突集合中
   * @param conflictingMethods
   * @param name
   * @param method
   */
  private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
    List<Method> list = conflictingMethods.get(name);
    if (list == null) {
      list = new ArrayList<Method>();
      conflictingMethods.put(name, list);
    }
    list.add(method);
  }
/**
   * 解決沖突
   * @param conflictingGetters
   */
  private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
    for (String propName : conflictingGetters.keySet()) {
      List<Method> getters = conflictingGetters.get(propName);
      Iterator<Method> iterator = getters.iterator();
      Method firstMethod = iterator.next();
      if (getters.size() == 1) {
        addGetMethod(propName, firstMethod);
      } else {
        Method getter = firstMethod;
//        獲取返回值類型
        Class<?> getterType = firstMethod.getReturnType();
        while (iterator.hasNext()) {
          Method method = iterator.next();
          Class<?> methodType = method.getReturnType();
          /**
           * 兩個(gè)方法的返回值類型一致,若兩個(gè)方法返回值類型均為boolean,則選取isXXX方法
           * 為getterType,則無法決定哪個(gè)方法更為合適,只能拋出異常
           *
           * */
          if (methodType.equals(getterType)) {
            throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property " 
                + propName + " in class " + firstMethod.getDeclaringClass()
                + ".  This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
            /**
             * getterType是methodType的子類,類型上更為具體
             * 則認(rèn)為當(dāng)前的getter 是合適的,無需做什么事情
             *
             * */
          } else if (methodType.isAssignableFrom(getterType)) {
            // OK getter type is descendant
            /**
            * methodType 是getterType的子類,此時(shí)認(rèn)為method方法更為合適,
             * 故將getter更新為method
             */
          } else if (getterType.isAssignableFrom(methodType)) {
            getter = method;
            getterType = methodType;
          } else {
            throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property " 
                + propName + " in class " + firstMethod.getDeclaringClass()
                + ".  This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
          }
        }
//       將篩選出的方法添加到getMethods中,并將方法返回值添加到getType中
        addGetMethod(propName, getter);
      }
    }
  }
  private void addGetMethod(String name, Method method) {
    if (isValidPropertyName(name)) {
//      解析返回值類型
      getMethods.put(name, new MethodInvoker(method));
//      將返回值類型由Type 轉(zhuǎn)為Class,并將轉(zhuǎn)換后的結(jié)果緩存到getTypes中
      getTypes.put(name, method.getReturnType());
    }
  }

如上,該處理getter沖突的的過程,代碼較長,在這里大家只要記住處理沖突的規(guī)則就能夠理解上面的邏輯:

  1. 沖突方法返回值類型具有繼承關(guān)系,則認(rèn)為子類的方法更加合適。
  2. 沖突方法返回值類型相同,則無法確定有用哪個(gè)方法,直接拋出異常。
  3. 沖突方法返回值類型完全不相關(guān),則無法確定有用哪個(gè)方法,拋出異常。

我們來看看MyBatis的測試用例理解下ReflectorTest

//*  ReflectorTest
  @Test
  public void testGetGetterType() throws Exception {
    Reflector reflector = Reflector.forClass(Section.class);
    Assert.assertEquals(Long.class, reflector.getGetterType("id"));
  }
    static interface Entity<T> {
    T getId();
    void setId(T id);

  }
static abstract class AbstractEntity implements Entity<Long> {
 
    private Long id;
 
    public Long getId() {
      return id;
    }

    public void setId(Long id) {
      this.id = id;
    }

  }

  static class Section extends AbstractEntity implements Entity<Long> {

  }

如上測試用例Section 類中有兩個(gè) getId() 方法,一個(gè)返回值為Long( java.lang.Long), 一個(gè)返回值類型為void (java.lang.Object)。由于
Long 類是Object的子類,故認(rèn)為Long 返回值類型對應(yīng)的方法更適合。
分析完getter方法的解析過程之后,我們接著來分析setter方法的解析過程。

setter 方法解析過程分析
//* Reflector
 private void addSetMethods(Class<?> cls) {
    Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
//    獲取當(dāng)前類,接口,以及父類中的方法。該方法邏輯不是很復(fù)雜,這里不展開
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
      String name = method.getName();
//      過濾出setter方法,且方法僅有一個(gè)參數(shù)
      if (name.startsWith("set") && name.length() > 3) {
        if (method.getParameterTypes().length == 1) {
          name = PropertyNamer.methodToProperty(name);
          /*
           *setter方法發(fā)生沖突原因是:可能存在重載情況,比如:
           * void setSex(int sex)
           * void setSex(SexEnum sex)
           */
          addMethodConflict(conflictingSetters, name, method);
        }
      }
    }
//    解決setter沖突
    resolveSetterConflicts(conflictingSetters);
  }

如上,與addGetMethods 方法的執(zhí)行流程類似,addSetMethods方法的執(zhí)行流程也分為如下幾個(gè)步驟:

  1. 獲取當(dāng)前類,接口,以及父類中的方法
  2. 過濾出setter方法其方法之后一個(gè)參數(shù)
  3. 獲取方法對應(yīng)的屬性名
  4. 將屬性名和其方法對象放入沖突集合中
  5. 解決setter沖突
    前四步相對而言比較簡單,我在此處就不展開分析了,我們來重點(diǎn)分析下解決setter沖突的邏輯。
/**
   * 解決setter沖突
   * @param conflictingSetters
   */
  private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
    for (String propName : conflictingSetters.keySet()) {
      List<Method> setters = conflictingSetters.get(propName);
      Method firstMethod = setters.get(0);
      if (setters.size() == 1) {
        addSetMethod(propName, firstMethod);
      } else {
        /*
         *獲取getter方法的返回值類型,由于getter方法不存在重載的情況,
         *所以可以用它的返回值類型反推哪個(gè)setter的更為合適
         */
        Class<?> expectedType = getTypes.get(propName);
        if (expectedType == null) {
          throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property "
              + propName + " in class " + firstMethod.getDeclaringClass() + ".  This breaks the JavaBeans " +
              "specification and can cause unpredicatble results.");
        } else {
          Iterator<Method> methods = setters.iterator();
          Method setter = null;
          while (methods.hasNext()) {
            Method method = methods.next();
//            獲取參數(shù)類型
            if (method.getParameterTypes().length == 1
                && expectedType.equals(method.getParameterTypes()[0])) {
//              參數(shù)類型和返回類型一致,則認(rèn)為是最好的選擇,并結(jié)束循環(huán)
              setter = method;
              break;
            }
          }
          if (setter == null) {
            throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property "
                + propName + " in class " + firstMethod.getDeclaringClass() + ".  This breaks the JavaBeans " +
                "specification and can cause unpredicatble results.");
          }
//          將篩選出的方法放入setMethods中,并將方法參數(shù)值添加到setTypes中
          addSetMethod(propName, setter);
        }
      }
    }
  }
	
private void addSetMethod(String name, Method method) {
    if (isValidPropertyName(name)) {
      setMethods.put(name, new MethodInvoker(method));
      setTypes.put(name, method.getParameterTypes()[0]);
    }
  }

如上,解決setter沖突執(zhí)行流程如下:

  1. 根據(jù)屬性名獲取其下面的方法集合,如果只有一個(gè)則直接返回,否則進(jìn)入沖突處理
  2. 進(jìn)入沖突處理分支之后首先獲取getter方法的返回值類型,由于getter方法不存在重載的情況,所以可以用它的返回值類型來反推哪個(gè)setter方法更合適
  3. 獲取setter方法的參數(shù)類型
  4. 如果setter方法的參數(shù)類型和其對應(yīng)的getter方法返回類型一致,則認(rèn)為是最好的選擇,并結(jié)束循環(huán)
  5. 如果找不到則拋出異常
小節(jié)

至此,我們對Reflector類的分析就全部完成,我們從按照三個(gè)方面對Reflector類進(jìn)行了分析,重點(diǎn)介紹了getter 的沖突處理和setter的沖突處理。
接下來,我們來分析下之前提到的PropertyTokenizer類,該類的主要作用是對復(fù)合屬性進(jìn)行分解。

PropertyTokenizer類分析
//* PropertyTokenizer

  //例子: person[0].birthdate.year
  private String name; //person
  private String indexedName; //person[0]
  private String index; //0
  private String children; //birthdate.year
  public PropertyTokenizer(String fullname) {
      //person[0].birthdate.year
      //找.(檢測傳入的參數(shù)中是否寶航了字符'.')
    int delim = fullname.indexOf('.');
    if (delim > -1) {
      /*
        以點(diǎn)位為界,進(jìn)行分割。比如:
        fullname=com.jay.mybatis
        以第一個(gè)點(diǎn)為分界符:
        name=com
        children=jay.mybatis
       */
      name = fullname.substring(0, delim);
      children = fullname.substring(delim + 1);
    } else {
        //找不到.的話,取全部部分
      name = fullname;
      children = null;
    }
    indexedName = name;
    //把中括號里的數(shù)字給解析出來
    delim = name.indexOf('[');
    if (delim > -1) {
      /*
      * 獲取中括號里的內(nèi)容,比如:
      * 1. 對于數(shù)組或List集合:[]中的內(nèi)容為數(shù)組下標(biāo),
      * 比如fullname=articles[1],index=1
      * 2.對于Map: []中的內(nèi)容為鍵,
      * 比如 fullname=xxxMap[keyName],index=keyName
      *
      * 關(guān)于 index 屬性的用法,可以參考 BaseWrapper 的 getCollectionValue 方法
      * */
      index = name.substring(delim + 1, name.length() - 1);
//      獲取分解符前面的內(nèi)容,比如 fullname=articles[1],name=articles
      name = name.substring(0, delim);
    }
  }


//* MetaClass
 public Class<?> getGetterType(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaClass metaProp = metaClassForProperty(prop);
      return metaProp.getGetterType(prop.getChildren());
    }
    // issue #506. Resolve the type inside a Collection Object
    return getGetterType(prop);
  }

如上,PropertyTokenizer類的核心邏輯就在其構(gòu)造器中,主要包括三部分邏輯

  1. 根據(jù) ‘.’ ,如果不能找到則取全部部分
  2. 能找到的話則首先截取 ’ .’ 符號之前的部分,把其余部分作為children。 然后通過MetaClass類的getGetterType的方法來循環(huán)提取。下面我們來看下MetaClassTest類的shouldCheckTypeForEachGetter測試用例

  @Test
  public void shouldCheckTypeForEachGetter() {
    MetaClass meta = MetaClass.forClass(RichType.class);
    assertEquals(String.class, meta.getGetterType("richField"));
    assertEquals(String.class, meta.getGetterType("richProperty"));
    assertEquals(List.class, meta.getGetterType("richList"));
    assertEquals(Map.class, meta.getGetterType("richMap"));
    assertEquals(List.class, meta.getGetterType("richList[0]"));

    assertEquals(RichType.class, meta.getGetterType("richType"));
    assertEquals(String.class, meta.getGetterType("richType.richField"));
    assertEquals(String.class, meta.getGetterType("richType.richProperty"));
    assertEquals(List.class, meta.getGetterType("richType.richList"));
    assertEquals(Map.class, meta.getGetterType("richType.richMap"));
    assertEquals(List.class, meta.getGetterType("richType.richList[0]"));
  }
public class RichType {

  private RichType richType;

  private String richField;

  private String richProperty;

  private Map richMap = new HashMap();

  private List richList = new ArrayList() {
    {
      add("bar");
    }
  };
  }
  //省略get,set方法
}

richType.richProperty 等作為復(fù)合屬性,通過PropertyTokenizer的處理同樣能提取到。
至此,對Setting 元素的源碼解析就全部完成了。

總結(jié)

本文篇幅較長,先是總體介紹了MyBatis的初始化過程,然后展開來講了properties元素的解析源碼和settings元素的解析源碼,其中在對settings進(jìn)行分析時(shí)又重點(diǎn)講了MetaClass類。在下一篇文章中,我將重點(diǎn)介紹其余幾個(gè)常用的元素 。希望對讀者朋友有所幫助。

參考文檔

MyBatis-源碼分析-配置文件解析過程
【深入淺出MyBatis系列十二】終結(jié)篇:MyBatis原理深入解析

源碼注釋以文檔地址:

https://github.com/XWxiaowei/mybatis




作者:碼農(nóng)飛哥
微信公眾號:碼農(nóng)飛哥