MyBatis 学习笔记(四)---源码分析篇---配置文件的解析过程(一)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 前几篇我们介绍了MyBatis的一些基本特性,对MyBatis有了个初步了解。接下来,我们将着手来分析一下MyBatis的源码,从源码层面复盘MyBatis的执行流程。

概述

前几篇我们介绍了MyBatis的一些基本特性,对MyBatis有了个初步了解。接下来,我们将着手来分析一下MyBatis的源码,从源码层面复盘MyBatis的执行流程。

思维导图概括

配置文件解析过程分析

有了上述思维导图,我们对配置文件文件的解析过程就有了一个大概的认识,下面我们就来具体分析下解析过程。

配置文件解析入口

首先,我们来看看调用MyBatis的示例代码

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

从上述示例代码中我们可以很清晰的看出,初始化过程是首先通过Resources 解析配置文件得到文件流。然后,将文件流传给SqlSessionFactoryBuilder的build方法,并最终得到sqlSessionFactory。

那么我们MyBatis的初始化入口就是SqlSessionFactoryBuilder的build 方法。

//* SqlSessionFactoryBuilder类
//以下3个方法都是调用下面第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.
      }
    }
  }
  //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

从上述源码,我们可以知道build 构建SqlSessionFactory 分为两步,首先 实例化一个XMLConfigBuilder,然后,调用XMLConfigBuilder的parse方法得到Configuration对象,最后将Configuration对象作为参数实例化一个DefaultSqlSessionFactory 即SqlSessionFactory对象。

接着往下看,下面我们来看看XMLConfigBuilder类。首先是实例化XMLConfigBuilder的过程。

//* XMLConfigBuilder
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  //上面6个构造函数最后都合流到这个函数,传入XPathParser
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //首先调用父类初始化Configuration
    super(new Configuration());
    //错误上下文设置成SQL Mapper Configuration(XML文件配置),以便后面出错了报错用吧
    ErrorContext.instance().resource("SQL Mapper Configuration");
    //将Properties全部设置到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 {
  //这个是DOM解析方式
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);
  //名称空间
      factory.setNamespaceAware(false);
  //忽略注释
      factory.setIgnoringComments(true);
  //忽略空白
      factory.setIgnoringElementContentWhitespace(false);
  //把 CDATA 节点转换为 Text 节点
      factory.setCoalescing(false);
  //扩展实体引用
      factory.setExpandEntityReferences(true);
      DocumentBuilder builder = factory.newDocumentBuilder();
  //需要注意的就是定义了EntityResolver(XMLMapperEntityResolver),这样不用联网去获取DTD,
  //将DTD放在org\apache\ibatis\builder\xml\mybatis-3-config.dtd,来达到验证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的实例化过程包括两个过程,1. 创建XPathParser的实例并初始化;2.创建Configuration的实例对象,然后将XPathParser的实例设置到XMLConfigBuilder中,而XPathParser 初始化主要做了两件事,初始化DocumentBuilder对象,并通过调用DocumentBuilder对象的parse方法得到Document对象,我们配置文件的配置就全部都转移到了Document对象中。我们下面通过调试看看Document 对象中的内容,测试用例是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());
  }

调试结果:

040fedcb9fc094619584d608f5dcf55b_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

31b8fddf5415d06f8b4988f4b09b67e4_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

介绍完XMLConfigBuilder的初始化过程之后,接着我们来看看XMLConfigBuilder中的parse()方法,由前面其初始化过程我们可以得知我们的配置信息已经保存到了XMLConfigBuilder的XPathParser对象的Document中了。解析来其实就是将XPathParser中的信息转移到Configuration对象中,不多说了,看看源码。

//* XMLConfigBuilder
  //解析配置
  public Configuration parse() {
    //如果已经解析过了,报错
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;    
    //根节点是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.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      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);
    }
  }

至此,一个MyBatis的解析过程就出来了,每个配置的解析逻辑封装在相应的方法中,接下来将重点介绍一些常用的配置,例如properties,settings,environments,typeAliases, typeHandler, mappers。闲话少叙,接下来我们首先来分析下properties的解析过程


解析properties配置

首先我们来看看一个普通的properties配置。

<properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
 </properties>
//* XMLConfigBuilder
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //如果在这些地方,属性多于一个的话,MyBatis 按照如下的顺序加载它们:
      //1.在 properties 元素体内指定的属性首先被读取。
      //2.从类路径下资源或 properties 元素的 url 属性中加载的属性第二被读取,它会覆盖已经存在的完全一样的属性。
      //3.作为方法参数传递的属性最后被读取, 它也会覆盖任一已经存在的完全一样的属性,这些属性可能是从 properties 元素体内和资源/url 属性中加载的。
      //传入方式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
      //1.XNode.getChildrenAsProperties函数方便得到孩子所有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) {
        //从文件系统中加载并解析属性文件
        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. 将属性值设置到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) {
//        设置属性到属性对象中
        properties.setProperty(name, value);
      }
    }
    return properties;
  }

代码中注释比较详实,代码结构不太复杂,读者们看下就会明白。不过需要特别说明:properties元素的解析顺序是:

1. 在Properties 元素体内指定的属性首先被读取。

2. 在类路径下资源或properties元素的url 属性中加载的属性第二个被读取,它会覆盖完全一样的属性

3. 作为方法参数传递的属性最后被读取,它也会覆盖任一已存在的完全一样的属性,这些属性可能是从properties 元素体内和资源 /url 属性中加载的。

//传入方式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props)


解析settings配置

settings 节点的解析过程

settings相关配置是MyBatis中非常重要的配置,这些配置用户调整MyBatis运行时的行为。settings配置繁多,在对这些配置不熟悉的情况下,保持默认的配置即可。详细的配置说明可以参考MyBatis官方文档setting

f5b2895aba47bb762b0c68cbbc1f1b18_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

我们先看看一个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子节点中的内容
      Properties props = context.getChildrenAsProperties();
      // 创建Configuration 类的"元信息"对象
      MetaClass metaConfig = MetaClass.forClass(Configuration.class);
      for (Object key : props.keySet()) {
        // Check that all settings are known to the configuration class
        //检查下是否在Configuration类里都有相应的setter方法(没有拼写错误)
        if (!metaConfig.hasSetter(String.valueOf(key))) {
          throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
      }

从上述源码中我们可以总结出setting 的解析主要分为如下几个步骤:


1.获取settings 子节点中的内容,这段代码在之前已经解释过,再次不在赘述。

2.然后就是创建Configuration类的“元信息”对象,在这一部分中出现了一个陌生的类MetaClass,我们一会在分析。

3.接着检查是否在Configuration类里都有相应的setter方法,不存在则抛出异常。

4.若通过MetaClass的检测,则将Properties中的信息设置到configuration对象中,逻辑结束

上述代码看似简单,实际上在第二步创建元信息对象还是蛮复杂的。接下来我们就来看看MetaClass类

MetaClass类源码解析

//*MetaClass
public class MetaClass {
    //有一个反射器
    //可以看到方法基本都是再次委派给这个Reflector
  private Reflector reflector;
  private MetaClass(Class type) {
//    根据类型创建Reflector
    this.reflector = Reflector.forClass(type);
  }
  public static MetaClass forClass(Class type) {
//  调用构造器方法
    return new MetaClass(type);
  }
  /**
   * 检查指定的属性是否有setter方法。
   * @param name
   * @return
   */
  public boolean hasSetter(String name) {
//    属性分词器,用于解析属性名
    PropertyTokenizer prop = new PropertyTokenizer(name);
//    hasNext返回true,则表明是一个复合属性
    if (prop.hasNext()) {
//      调用reflector的hasSetter方法
      if (reflector.hasSetter(prop.getName())) {
//        为属性创建MetaClass
        MetaClass metaProp = metaClassForProperty(prop.getName());
//        再次调用hasSetter
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      // 非复合属性则直接调用hasSetter一次即可
      return reflector.hasSetter(prop.getName());
    }
  }
  public MetaClass metaClassForProperty(String name) {
    Class propType = reflector.getGetterType(name);
    return MetaClass.forClass(propType);
  }

从源码我们可以看出MetaClass 的forClass 方法最终委托给了这个Reflector的forClass方法。而hasSetter 方法中又调用了reflector的hasSetter方法,那么Reflector类内部实现如何呢?同时我们还注意到出现了一个新的类PropertyTokenizer,那么这个类内部实现如何呢?我们待会再来分析下。首先我们简单介绍下这几个类。


Reflector -----> 反射器,用于解析和存储目标类的元信息

PropertyTokenizer -----> 属性分词器,用于解析属性名。

接下来,我们来看看Reflector的相关实现。


Reflector类源码解析

Reflector 类的源码较多,在此处我们不做一一分析。我主要从以下三个方面:

1.Reflector的构造方法和成员变量分析

2.getter 方法解析过程分析

3.setter 方法解析过程分析

//* Reflector
  private static boolean classCacheEnabled = true;
  private static final String[] EMPTY_STRING_ARRAY = new String[0];
  //这里用ConcurrentHashMap,多线程支持,作为一个缓存
  private static final Map, Reflector> REFLECTOR_MAP = new ConcurrentHashMap, Reflector>();
  private Class type;
  //getter的属性列表
  private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
  //setter的属性列表
  private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
  //setter的方法列表
  private Map setMethods = new HashMap();
  //getter的方法列表
  private Map getMethods = new HashMap();
  //setter的类型列表
  private Map> setTypes = new HashMap>();
  //getter的类型列表
  private Map> getTypes = new HashMap>();
  //构造函数
  private Constructor defaultConstructor;
  private Map caseInsensitivePropertyMap = new HashMap();
  /**
   * 得到某个类的反射器,是静态方法,而且要缓存,
   * 又要多线程,所以REFLECTOR_MAP是一个ConcurrentHashMap
   */
  public static Reflector forClass(Class clazz) {
    if (classCacheEnabled) {
      // synchronized (clazz) removed see issue #461
        //对于每个类来说,我们假设它是不会变的,这样可以考虑将这个类的信息
      // (构造函数,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;
    //解析目标类的默认构造方法,并赋值给defaultConstructor变量
    addDefaultConstructor(clazz);
    //解析getter,并将解析结果放入getMethods中
    addGetMethods(clazz);
    //解析setter方法,并将解析结果放入setMethods中
    addSetMethods(clazz);
    //解析属性字段,并将解析结果添加到setMethods或getMethods中
    addFields(clazz);
//    从getMethods映射中获取可读属性名数组
    readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
//    从setMethods 映射中获取可写属性名数组
    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 定义了一个ConcurrentHashMap 用于缓存每个类的反射器,以提高速度。我们知道ConcurrentHashMap是一个线程安全类,所以不存在线程安全问题。同时,其他的集合用于存储getter,setter 方法的相关信息。构造器里会讲元信息里里的构造方法,属性字段,setter方法,getter方法设置到相应的集合中。

接下来,我们来分析下getter方法。


getter方法解析过程分析

//* Reflector
  private void addGetMethods(Class cls) {
    Map> conflictingGetters = new HashMap>();
//    获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
//      getter方法不应该有参数,若存在参数,则忽略当前方法
      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方法名转成相应的属性,比如 getName -> name
          name = PropertyNamer.methodToProperty(name);
/*         将冲突的方法添加到conflictingGetters中,考虑这样一种情况
          getTitle和isTitle两个方法经过methodToProperty处理,
          均得到 name=title,这会导致冲突
          对于冲突的方法,这里想统一存起来,后续在解决冲突
          */
          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 方法的的执行流程如下:


1.获取当前类,接口,以及父类中的方法

2.遍历上一步获取的方法数组,并过滤出以get和is开头方法

3.根据方法名截取出属性名

4.将冲突的属性名和方法对象添加到冲突集合中

5.处理getter冲突,筛选出合适的方法。

我们知道getter截取属性冲突主要是由于 getXXX() 和isXXX() 两种类型的方法,截取属性后会冲突。

比较核心的知识点就是处理getter 冲突,接下来,我们就来看看相应的源码

//* Reflector
  /**
   * //  添加属性名和方法对象到冲突集合中
   * @param conflictingMethods
   * @param name
   * @param method
   */
  private void addMethodConflict(Map> conflictingMethods, String name, Method method) {
    List list = conflictingMethods.get(name);
    if (list == null) {
      list = new ArrayList();
      conflictingMethods.put(name, list);
    }
    list.add(method);
  }
/**
   * 解决冲突
   * @param conflictingGetters
   */
  private void resolveGetterConflicts(Map> conflictingGetters) {
    for (String propName : conflictingGetters.keySet()) {
      List getters = conflictingGetters.get(propName);
      Iterator 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();
          /**
           * 两个方法的返回值类型一致,若两个方法返回值类型均为boolean,则选取isXXX方法
           * 为getterType,则无法决定哪个方法更为合适,只能抛出异常
           *
           * */
          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的子类,类型上更为具体
             * 则认为当前的getter 是合适的,无需做什么事情
             *
             * */
          } else if (methodType.isAssignableFrom(getterType)) {
            // OK getter type is descendant
            /**
            * methodType 是getterType的子类,此时认为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 转为Class,并将转换后的结果缓存到getTypes中
      getTypes.put(name, method.getReturnType());
    }
  }

如上,该处理getter冲突的的过程,代码较长,在这里大家只要记住处理冲突的规则就能够理解上面的逻辑:

1.冲突方法返回值类型具有继承关系,则认为子类的方法更加合适。

2.冲突方法返回值类型相同,则无法确定有用哪个方法,直接抛出异常。

3.冲突方法返回值类型完全不相关,则无法确定有用哪个方法,抛出异常。

我们来看看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 getId();
    void setId(T id);
  }
static abstract class AbstractEntity implements Entity {
    private Long id;
    public Long getId() {
      return id;
    }
    public void setId(Long id) {
      this.id = id;
    }
  }
  static class Section extends AbstractEntity implements Entity {
  }

如上测试用例Section 类中有两个 getId() 方法,一个返回值为Long( java.lang.Long), 一个返回值类型为void (java.lang.Object)。由于

Long 类是Object的子类,故认为Long 返回值类型对应的方法更适合。

分析完getter方法的解析过程之后,我们接着来分析setter方法的解析过程。


setter 方法解析过程分析

//* Reflector
 private void addSetMethods(Class cls) {
    Map> conflictingSetters = new HashMap>();
//    获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里不展开
    Method[] methods = getClassMethods(cls);
    for (Method method : methods) {
      String name = method.getName();
//      过滤出setter方法,且方法仅有一个参数
      if (name.startsWith("set") && name.length() > 3) {
        if (method.getParameterTypes().length == 1) {
          name = PropertyNamer.methodToProperty(name);
          /*
           *setter方法发生冲突原因是:可能存在重载情况,比如:
           * void setSex(int sex)
           * void setSex(SexEnum sex)
           */
          addMethodConflict(conflictingSetters, name, method);
        }
      }
    }
//    解决setter冲突
    resolveSetterConflicts(conflictingSetters);
  }

如上,与addGetMethods 方法的执行流程类似,addSetMethods方法的执行流程也分为如下几个步骤:

1.获取当前类,接口,以及父类中的方法

2.过滤出setter方法其方法之后一个参数

3.获取方法对应的属性名

4.将属性名和其方法对象放入冲突集合中

5.解决setter冲突

前四步相对而言比较简单,我在此处就不展开分析了,我们来重点分析下解决setter冲突的逻辑。

/**
   * 解决setter冲突
   * @param conflictingSetters
   */
  private void resolveSetterConflicts(Map> conflictingSetters) {
    for (String propName : conflictingSetters.keySet()) {
      List setters = conflictingSetters.get(propName);
      Method firstMethod = setters.get(0);
      if (setters.size() == 1) {
        addSetMethod(propName, firstMethod);
      } else {
        /*
         *获取getter方法的返回值类型,由于getter方法不存在重载的情况,
         *所以可以用它的返回值类型反推哪个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 methods = setters.iterator();
          Method setter = null;
          while (methods.hasNext()) {
            Method method = methods.next();
//            获取参数类型
            if (method.getParameterTypes().length == 1
                && expectedType.equals(method.getParameterTypes()[0])) {
//              参数类型和返回类型一致,则认为是最好的选择,并结束循环
              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中,并将方法参数值添加到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冲突执行流程如下:

1.根据属性名获取其下面的方法集合,如果只有一个则直接返回,否则进入冲突处理

2.进入冲突处理分支之后首先获取getter方法的返回值类型,由于getter方法不存在重载的情况,所以可以用它的返回值类型来反推哪个setter方法更合适

3.获取setter方法的参数类型

4.如果setter方法的参数类型和其对应的getter方法返回类型一致,则认为是最好的选择,并结束循环

5.如果找不到则抛出异常

小节

至此,我们对Reflector类的分析就全部完成,我们从按照三个方面对Reflector类进行了分析,重点介绍了getter 的冲突处理和setter的冲突处理。

接下来,我们来分析下之前提到的PropertyTokenizer类,该类的主要作用是对复合属性进行分解。


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

     //找.(检测传入的参数中是否宝航了字符'.')

   int delim = fullname.indexOf('.');

   if (delim > -1) {

     /*

       以点位为界,进行分割。比如:

       fullname=com.jay.mybatis

       以第一个点为分界符:

       name=com

       children=jay.mybatis

      */

     name = fullname.substring(0, delim);

     children = fullname.substring(delim + 1);

   } else {

       //找不到.的话,取全部部分

     name = fullname;

     children = null;

   }

   indexedName = name;

   //把中括号里的数字给解析出来

   delim = name.indexOf('[');

   if (delim > -1) {

     /*

     * 获取中括号里的内容,比如:

     * 1. 对于数组或List集合:[]中的内容为数组下标,

     * 比如fullname=articles[1],index=1

     * 2.对于Map: []中的内容为键,

     * 比如 fullname=xxxMap[keyName],index=keyName

     *

     * 关于 index 属性的用法,可以参考 BaseWrapper 的 getCollectionValue 方法

     * */

     index = name.substring(delim + 1, name.length() - 1);

//      获取分解符前面的内容,比如 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类的核心逻辑就在其构造器中,主要包括三部分逻辑

1.根据 ‘.’ ,如果不能找到则取全部部分

2.能找到的话则首先截取 ’ .’ 符号之前的部分,把其余部分作为children。 然后通过MetaClass类的getGetterType的方法来循环提取。下面我们来看下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 等作为复合属性,通过PropertyTokenizer的处理同样能提取到。

至此,对Setting 元素的源码解析就全部完成了。

总结

本文篇幅较长,先是总体介绍了MyBatis的初始化过程,然后展开来讲了properties元素的解析源码和settings元素的解析源码,其中在对settings进行分析时又重点讲了MetaClass类。在下一篇文章中,我将重点介绍其余几个常用的元素 。希望对读者朋友有所帮助。

参考文档

MyBatis-源码分析-配置文件解析过程

【深入浅出MyBatis系列十二】终结篇:MyBatis原理深入解析

源码注释以文档地址:

https://github.com/XWxiaowei/mybatis

相关文章
|
1月前
|
存储 安全 网络协议
Elasticsearch 配置文件解析
【10月更文挑战第3天】Elasticsearch 配置文件解析
63 3
|
2月前
|
人工智能 前端开发 Java
【Tomcat源码分析】启动过程深度解析 (二)
本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。
【Tomcat源码分析】启动过程深度解析 (二)
|
1月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
105 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
2月前
|
Rust Python
Python 解析 toml 配置文件
Python 解析 toml 配置文件
38 1
|
2月前
|
Python
Python 解析 yaml 配置文件
Python 解析 yaml 配置文件
37 0
|
2月前
|
Python
Python 解析 ini 配置文件
Python 解析 ini 配置文件
33 0
|
3月前
|
机器学习/深度学习 计算机视觉 Python
深度学习项目中在yaml文件中定义配置,以及使用的python的PyYAML库包读取解析yaml配置文件
深度学习项目中在yaml文件中定义配置,以及使用的python的PyYAML库包读取解析yaml配置文件
93 0
|
3月前
|
网络协议 Ubuntu Linux
在Linux中,设置DNS需要修改哪个配置文件?
在Linux中,设置DNS需要修改哪个配置文件?
|
28天前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
63 0
|
28天前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
49 0

推荐镜像

更多