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

概述


今天我们着手来分析一下MyBatis的源码,从源码层面复盘一下MyBatis配置文件的解析过程,然后重点介绍几个核心配置。

思维导图概括

首先通过一张思维导图来大致了解下MyBatis的初始化过程(对配置文件的解析过程)

MyBatis 源码分析篇---配置文件的解析过程_初始化_02在这里插入图片描述

配置文件解析过程分析

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

配置文件解析入口

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

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

如上,解析配置文件初始化的调用比较简单,首先是通过Resources 解析配置文件得到文件流。然后,将文件流传给SqlSessionFactoryBuilder的build方法,并最终得到sqlSessionFactory。

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

 1//* SqlSessionFactoryBuilder类
2//以下3个方法都是调用下面第8种方法
3 public SqlSessionFactory build(InputStream inputStream) {
4 return build(inputStream, null, null);
5 }
6 public SqlSessionFactory build(InputStream inputStream, String environment) {
7 return build(inputStream, environment, null);
8 }
9
10 public SqlSessionFactory build(InputStream inputStream, Properties properties) {
11 return build(inputStream, null, properties);
12 }
13 //第8种方法和第4种方法差不多,Reader换成了InputStream
14 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
15 try {
16 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
17 return build(parser.parse());
18 } catch (Exception e) {
19 throw ExceptionFactory.wrapException("Error building SqlSession.", e);
20 } finally {
21 ErrorContext.instance().reset();
22 try {
23 inputStream.close();
24 } catch (IOException e) {
25 // Intentionally ignore. Prefer previous error.
26 }
27 }
28 }
29 //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
30 public SqlSessionFactory build(Configuration config) {
31 return new DefaultSqlSessionFactory(config);
32 }

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

需要注意的是SqlSessionFactoryBuilder类中的build方法被进行了多次重载,按照传入的主参数来分则分为两类1. 传入Reader参数的;2. 传入InputStream参数的。

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

 1//* XMLConfigBuilder
2 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
3 //构造一个需要验证,XMLMapperEntityResolver的XPathParser
4 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
5 }
6
7 //上面6个构造函数最后都合流到这个函数,传入XPathParser
8 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
9 //首先调用父类初始化Configuration
10 super(new Configuration());
11 //错误上下文设置成SQL Mapper Configuration(XML文件配置),以便后面出错了报错用吧
12 ErrorContext.instance().resource("SQL Mapper Configuration");
13 //将Properties全部设置到Configuration里面去
14 this.configuration.setVariables(props);
15 this.parsed = false;
16 this.environment = environment;
17 this.parser = parser;
18 }
19//* XPathParser
20 public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
21 commonConstructor(validation, variables, entityResolver);
22 this.document = createDocument(new InputSource(reader));
23 }
24 private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
25 this.validation = validation;
26 this.entityResolver = entityResolver;
27 this.variables = variables;
28 //共通构造函数,除了把参数都设置到实例变量里面去以外,还初始化了XPath
29 XPathFactory factory = XPathFactory.newInstance();
30 this.xpath = factory.newXPath();
31 }
32
33 private Document createDocument(InputSource inputSource) {
34 try {
35 //这个是DOM解析方式
36 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
37 factory.setValidating(validation);
38 //名称空间
39 factory.setNamespaceAware(false);
40 //忽略注释
41 factory.setIgnoringComments(true);
42 //忽略空白
43 factory.setIgnoringElementContentWhitespace(false);
44 //把 CDATA 节点转换为 Text 节点
45 factory.setCoalescing(false);
46 //扩展实体引用
47 factory.setExpandEntityReferences(true);
48 DocumentBuilder builder = factory.newDocumentBuilder();
49 //需要注意的就是定义了EntityResolver(XMLMapperEntityResolver),这样不用联网去获取DTD,
50 //将DTD放在org\apache\ibatis\builder\xml\mybatis-3-config.dtd,来达到验证xml合法性的目的
51 // 省略其他代码
52 return builder.parse(inputSource);
53 } catch (Exception e) {
54 throw new BuilderException("Error creating document instance. Cause: " + e, e);
55 }
56 }

从上述源码中,我们可以看出在XMLConfigBuilder的实例化过程包括两个过程,1. 创建XPathParser的实例并初始化;2.创建Configuration的实例对象,然后将XPathParser的实例设置到XMLConfigBuilder中。而在XPathParser 的初始化过程主要做了两件事,

  1. 把参数设置到实例变量并初始化XPath

  2. 初始化DocumentBuilder对象,并通过调用DocumentBuilder对象的parse方法得到Document对象。
    我们配置文件的配置就全部都转移到了Document对象中。
    下面我们通过调试看看Document 对象中的内容,测试用例是MyBatis 自身的单元测试XPathParserTest
    测试的xml

 1<!--
2nodelet_test.xml
3-->
4<employee id="${id_var}">
5 <blah something="that"/>
6 <first_name>Jim</first_name>
7 <last_name>Smith</last_name>
8 <birth_date>
9 <year>1970</year>
10 <month>6</month>
11 <day>15</day>
12 </birth_date>
13 <height units="ft">5.8</height>
14 <weight units="lbs">200</weight>
15 <active>true</active>
16</employee>

测试用例:

 1//* XPathParserTest
2 @Test
3 public void shouldTestXPathParserMethods() throws Exception {
4 String resource = "resources/nodelet_test.xml";
5 InputStream inputStream = Resources.getResourceAsStream(resource);
6 XPathParser parser = new XPathParser(inputStream, false, null, null);
7 assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
8 assertEquals("<id>${id_var}</id>", parser.evalNode("/employee/@id").toString().trim());
9 assertEquals(7, parser.evalNodes("/employee/*").size());
10 XNode node = parser.evalNode("/employee/height");
11 assertEquals("employee/height", node.getPath());
12 assertEquals("employee[${id_var}]_height", node.getValueBasedIdentifier());
13 }

如上,XPathParser通过调用evalNodes,evalString等方法获取节点的值以及子节点。底层最终调用的方法是

1 private Object evaluate(String expression, Object root, QName returnType) {
2 try {
3 //最终合流到这儿,直接调用XPath.evaluate
4 return xpath.evaluate(expression, root, returnType);
5 } catch (Exception e) {
6 throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
7 }
8 }

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

 1//* XMLConfigBuilder
2 //解析配置
3 public Configuration parse() {
4 //如果已经解析过了,报错
5 if (parsed) {
6 throw new BuilderException("Each XMLConfigBuilder can only be used once.");
7 }
8 parsed = true;
9 //根节点是configuration
10 parseConfiguration(parser.evalNode("/configuration"));
11 return configuration;
12 }
13 //解析配置
14 private void parseConfiguration(XNode root) {
15 try {
16 //分步骤解析
17 //1.properties
18 propertiesElement(root.evalNode("properties"));
19 //6.设置
20 settingsElement(root.evalNode("settings"));
21 //省略其他代码
22 } catch (Exception e) {
23 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
24 }
25 }

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

解析properties配置

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

1  <properties resource="org/mybatis/example/config.properties">
2 <property name="username" value="dev_user"/>
3 <property name="password" value="F2Fa3!33TYyg"/>
4 </properties>
1//* XMLConfigBuilder
2 private void propertiesElement(XNode context) throws Exception {
3 if (context != null) {
4 //1.XNode.getChildrenAsProperties函数方便得到孩子所有Properties
5 Properties defaults = context.getChildrenAsProperties();
6 //2.然后查找resource或者url,加入前面的Properties
7 String resource = context.getStringAttribute("resource");
8 String url = context.getStringAttribute("url");
9 if (resource != null && url != null) {
10 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
11 }
12 if (resource != null) {
13 //从文件系统中加载并解析属性文件
14 defaults.putAll(Resources.getResourceAsProperties(resource));
15 } else if (url != null) {
16 //通过url加载并解析属性文件
17 defaults.putAll(Resources.getUrlAsProperties(url));
18 }
19 //3.Variables也全部加入Properties
20 Properties vars = configuration.getVariables();
21 if (vars != null) {
22 defaults.putAll(vars);
23 }
24 parser.setVariables(defaults);
25 //4. 将属性值设置到configuration中
26 configuration.setVariables(defaults);
27 }
28 }


代码中注释的比较详实,代码结构也不太复杂,读者朋友们看下就会明白。不过需要特别说明: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

MyBatis 源码分析篇---配置文件的解析过程_配置文件_03MyBatis中settings配置说明

我们先看看一个settings 的简单配置

1<settings>
2 <setting name="cacheEnabled" value="true"/>
3 <setting name="lazyLoadingEnabled" value="true"/>
4 <setting name="multipleResultSetsEnabled" value="true"/>
5</settings>

setting的解析源码

接下来我们来看看setting的解析源码。

 1 //*XMLConfigBuilder
2 private void settingsElement(XNode context) throws Exception {
3 if (context != null) {
4// 获取settings子节点中的内容
5 Properties props = context.getChildrenAsProperties();
6 // 创建Configuration 类的"元信息"对象
7 MetaClass metaConfig = MetaClass.forClass(Configuration.class);
8 for (Object key : props.keySet()) {
9 // Check that all settings are known to the configuration class
10 //检查下是否在Configuration类里都有相应的setter方法(没有拼写错误)
11 if (!metaConfig.hasSetter(String.valueOf(key))) {
12 throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
13 }
14 }

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


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

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

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

  4. 若通过MetaClass的检测,则将Properties中的信息设置到configuration对象中,逻辑结束。
    上述代码看似简单,实际上在第二步创建元信息对象还是蛮复杂的。接下来我们就来看看MetaClass类


MetaClass类的源码解析
 1//*MetaClass
2public class MetaClass {
3 //有一个反射器
4 //可以看到方法基本都是再次委派给这个Reflector
5 private Reflector reflector;
6 private MetaClass(Class<?> type) {
7// 根据类型创建Reflector
8 this.reflector = Reflector.forClass(type);
9 }
10 public static MetaClass forClass(Class<?> type) {
11// 调用构造器方法
12 return new MetaClass(type);
13 }
14 /**
15 * 检查指定的属性是否有setter方法。
16 * @param name
17 * @return
18 */
19 public boolean hasSetter(String name) {
20// 属性分词器,用于解析属性名
21 PropertyTokenizer prop = new PropertyTokenizer(name);
22// hasNext返回true,则表明是一个复合属性
23 if (prop.hasNext()) {
24// 调用reflector的hasSetter方法
25 if (reflector.hasSetter(prop.getName())) {
26// 为属性创建MetaClass
27 MetaClass metaProp = metaClassForProperty(prop.getName());
28// 再次调用hasSetter
29 return metaProp.hasSetter(prop.getChildren());
30 } else {
31 return false;
32 }
33 } else {
34 // 非复合属性则直接调用hasSetter一次即可
35 return reflector.hasSetter(prop.getName());
36 }
37 }
38 public MetaClass metaClassForProperty(String name) {
39 Class<?> propType = reflector.getGetterType(name);
40 return MetaClass.forClass(propType);
41 }

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

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

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

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

Reflector类源码解析

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

  1. Reflector的构造方法和成员变量分析
  2. getter 方法解析过程分析
  3. setter 方法解析过程分析
 1//* Reflector
2 private static boolean classCacheEnabled = true;
3 private static final String[] EMPTY_STRING_ARRAY = new String[0];
4 //这里用ConcurrentHashMap,多线程支持,作为一个缓存
5 private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<Class<?>, Reflector>();
6
7 private Class<?> type;
8 //getter的属性列表
9 private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
10 //setter的属性列表
11 private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
12 //setter的方法列表
13 private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
14 //getter的方法列表
15 private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
16 //setter的类型列表
17 private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
18 //getter的类型列表
19 private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
20 //构造函数
21 private Constructor<?> defaultConstructor;
22
23 private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
24
25 /**
26 * 得到某个类的反射器,是静态方法,而且要缓存,
27 * 又要多线程,所以REFLECTOR_MAP是一个ConcurrentHashMap
28 */
29 public static Reflector forClass(Class<?> clazz) {
30 if (classCacheEnabled) {
31 // synchronized (clazz) removed see issue #461
32 //对于每个类来说,我们假设它是不会变的,这样可以考虑将这个类的信息
33 // (构造函数,getter,setter,字段)加入缓存,以提高速度
34 Reflector cached = REFLECTOR_MAP.get(clazz);
35 if (cached == null) {
36 cached = new Reflector(clazz);
37 REFLECTOR_MAP.put(clazz, cached);
38 }
39 return cached;
40 } else {
41 return new Reflector(clazz);
42 }
43 }
44
45 private Reflector(Class<?> clazz) {
46 type = clazz;
47 //解析目标类的默认构造方法,并赋值给defaultConstructor变量
48 addDefaultConstructor(clazz);
49 //解析getter,并将解析结果放入getMethods中
50 addGetMethods(clazz);
51 //解析setter方法,并将解析结果放入setMethods中
52 addSetMethods(clazz);
53 //解析属性字段,并将解析结果添加到setMethods或getMethods中
54 addFields(clazz);
55// 从getMethods映射中获取可读属性名数组
56 readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
57// 从setMethods 映射中获取可写属性名数组
58 writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
59 //将所有属性名的大写形式作为键,属性名作为值,存入到caseInsensitivePropertyMap中
60 for (String propName : readablePropertyNames) {
61 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
62 }
63 for (String propName : writeablePropertyNames) {
64 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
65 }
66 }
67//省略其他方法

如上,Reflector 定义了一个ConcurrentHashMap 用于缓存每个类的反射器,以提高速度。我们知道ConcurrentHashMap是一个线程安全类,所以不存在线程安全问题。同时,其他的集合用于存储getter,setter 方法的相关信息。构造器里会讲元信息里里的构造方法,属性字段,setter方法,getter方法设置到相应的集合中。

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

getter方法解析过程分析
 1//* Reflector
2 private void addGetMethods(Class<?> cls) {
3 Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
4// 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂
5 Method[] methods = getClassMethods(cls);
6 for (Method method : methods) {
7// getter方法不应该有参数,若存在参数,则忽略当前方法
8 if (method.getParameterTypes().length > 0) {
9 continue;
10 }
11 String name = method.getName();
12// 过滤出以get或is开头的方法
13 if (name.startsWith("get") && name.length() > 3) {
14 if (method.getParameterTypes().length == 0) {
15// 将getXXX方法名转成相应的属性,比如 getName -> name
16 name = PropertyNamer.methodToProperty(name);
17/* 将冲突的方法添加到conflictingGetters中,考虑这样一种情况
18 getTitle和isTitle两个方法经过methodToProperty处理,
19 均得到 name=title,这会导致冲突
20 对于冲突的方法,这里想统一存起来,后续在解决冲突
21 */
22 addMethodConflict(conflictingGetters, name, method);
23 }
24 } else if (name.startsWith("is") && name.length() > 2) {
25 if (method.getParameterTypes().length == 0) {
26 name = PropertyNamer.methodToProperty(name);
27 addMethodConflict(conflictingGetters, name, method);
28 }
29 }
30 }
31// 处理getter冲突
32 resolveGetterConflicts(conflictingGetters);
33 }

如上, addGetMethods 方法的的执行流程如下:

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

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

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

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

  5. 处理getter冲突,筛选出合适的方法。
    我们知道getter截取属性冲突主要是由于 getXXX() 和isXXX() 两种类型的方法,截取属性后会冲突。
    比较核心的知识点就是处理getter 冲突,接下来,我们就来看看相应的源码

 1//* Reflector
2
3 /**
4 * // 添加属性名和方法对象到冲突集合中
5 * @param conflictingMethods
6 * @param name
7 * @param method
8 */
9 private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
10 List<Method> list = conflictingMethods.get(name);
11 if (list == null) {
12 list = new ArrayList<Method>();
13 conflictingMethods.put(name, list);
14 }
15 list.add(method);
16 }
17/**
18 * 解决冲突
19 * @param conflictingGetters
20 */
21 private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
22 for (String propName : conflictingGetters.keySet()) {
23 List<Method> getters = conflictingGetters.get(propName);
24 Iterator<Method> iterator = getters.iterator();
25 Method firstMethod = iterator.next();
26 if (getters.size() == 1) {
27 addGetMethod(propName, firstMethod);
28 } else {
29 Method getter = firstMethod;
30// 获取返回值类型
31 Class<?> getterType = firstMethod.getReturnType();
32 while (iterator.hasNext()) {
33 Method method = iterator.next();
34 Class<?> methodType = method.getReturnType();
35 /**
36 * 两个方法的返回值类型一致,若两个方法返回值类型均为boolean,则选取isXXX方法
37 * 为getterType,则无法决定哪个方法更为合适,只能抛出异常
38 *
39 * */
40 if (methodType.equals(getterType)) {
41 throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
42 + propName + " in class " + firstMethod.getDeclaringClass()
43 + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
44 /**
45 * getterType是methodType的子类,类型上更为具体
46 * 则认为当前的getter 是合适的,无需做什么事情
47 *
48 * */
49 } else if (methodType.isAssignableFrom(getterType)) {
50 // OK getter type is descendant
51 /**
52 * methodType 是getterType的子类,此时认为method方法更为合适,
53 * 故将getter更新为method
54 */
55 } else if (getterType.isAssignableFrom(methodType)) {
56 getter = method;
57 getterType = methodType;
58 } else {
59 throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
60 + propName + " in class " + firstMethod.getDeclaringClass()
61 + ". This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
62 }
63 }
64// 将筛选出的方法添加到getMethods中,并将方法返回值添加到getType中
65 addGetMethod(propName, getter);
66 }
67 }
68 }
69 private void addGetMethod(String name, Method method) {
70 if (isValidPropertyName(name)) {
71// 解析返回值类型
72 getMethods.put(name, new MethodInvoker(method));
73// 将返回值类型由Type 转为Class,并将转换后的结果缓存到getTypes中
74 getTypes.put(name, method.getReturnType());
75 }
76 }

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


  1. 冲突方法返回值类型具有继承关系,则认为子类的方法更加合适。
  2. 冲突方法返回值类型相同,则无法确定有用哪个方法,直接抛出异常。
  3. 冲突方法返回值类型完全不相关,则无法确定有用哪个方法,抛出异常。

我们来看看MyBatis的测试用例理解下ReflectorTest

 1//*  ReflectorTest
2 @Test
3 public void testGetGetterType() throws Exception {
4 Reflector reflector = Reflector.forClass(Section.class);
5 Assert.assertEquals(Long.class, reflector.getGetterType("id"));
6 }
7 static interface Entity<T> {
8 T getId();
9 void setId(T id);
10
11 }
12static abstract class AbstractEntity implements Entity<Long> {
13
14 private Long id;
15
16 public Long getId() {
17 return id;
18 }
19
20 public void setId(Long id) {
21 this.id = id;
22 }
23
24 }
25
26 static class Section extends AbstractEntity implements Entity<Long> {
27
28 }

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

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

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

setter 方法解析过程分析
 1//* Reflector
2 private void addSetMethods(Class<?> cls) {
3 Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
4// 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里不展开
5 Method[] methods = getClassMethods(cls);
6 for (Method method : methods) {
7 String name = method.getName();
8// 过滤出setter方法,且方法仅有一个参数
9 if (name.startsWith("set") && name.length() > 3) {
10 if (method.getParameterTypes().length == 1) {
11 name = PropertyNamer.methodToProperty(name);
12 /*
13 *setter方法发生冲突原因是:可能存在重载情况,比如:
14 * void setSex(int sex)
15 * void setSex(SexEnum sex)
16 */
17 addMethodConflict(conflictingSetters, name, method);
18 }
19 }
20 }
21// 解决setter冲突
22 resolveSetterConflicts(conflictingSetters);
23 }

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


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

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

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

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

  5. 解决setter冲突
    前四步相对而言比较简单,我在此处就不展开分析了,
    限于篇幅,我这里说下解决setter冲突执行流程如下:

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

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

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

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

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


小节

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

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

PropertyTokenizer类分析
 1//* PropertyTokenizer
2
3 //例子:person[0].birthdate.year
4 private String name; //person
5 private String indexedName; //person[0]
6 private String index; //0
7 private String children; //birthdate.year
8 public PropertyTokenizer(String fullname) {
9 //person[0].birthdate.year
10 //找.(检测传入的参数中是否宝航了字符'.')
11 int delim = fullname.indexOf('.');
12 if (delim > -1) {
13 /*
14 以点位为界,进行分割。比如:
15 fullname=com.jay.mybatis
16 以第一个点为分界符:
17 name=com
18 children=jay.mybatis
19 */
20 name = fullname.substring(0, delim);
21 children = fullname.substring(delim + 1);
22 } else {
23 //找不到.的话,取全部部分
24 name = fullname;
25 children = null;
26 }
27 indexedName = name;
28 //把中括号里的数字给解析出来
29 delim = name.indexOf('[');
30 if (delim > -1) {
31 /*
32 * 获取中括号里的内容,比如:
33 * 1. 对于数组或List集合:[]中的内容为数组下标,
34 * 比如fullname=articles[1],index=1
35 * 2.对于Map: []中的内容为键,
36 * 比如 fullname=xxxMap[keyName],index=keyName
37 *
38 * 关于 index 属性的用法,可以参考 BaseWrapper 的 getCollectionValue 方法
39 * */
40 index = name.substring(delim + 1, name.length() - 1);
41// 获取分解符前面的内容,比如 fullname=articles[1],name=articles
42 name = name.substring(0, delim);
43 }
44 }
45
46
47//* MetaClass
48 public Class<?> getGetterType(String name) {
49 PropertyTokenizer prop = new PropertyTokenizer(name);
50 if (prop.hasNext()) {
51 MetaClass metaProp = metaClassForProperty(prop);
52 return metaProp.getGetterType(prop.getChildren());
53 }
54 // issue #506. Resolve the type inside a Collection Object
55 return getGetterType(prop);
56 }

如上,PropertyTokenizer类的核心逻辑就在其构造器中,主要包括三部分逻辑


  1. 根据 '.' ,如果不能找到则取全部部分。
  2. 能找到的话则首先截取 ' .' 符号之前的部分,把其余部分作为children。然后通过MetaClass类的getGetterType的方法来循环提取。下面我们来看下MetaClassTest类的shouldCheckTypeForEachGetter测试用例。

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

总结

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

源码注释以文档地址:

https://github.com/XWxiaowei/mybatis