概述
前几篇我们介绍了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()); }
调试结果:
介绍完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
我们先看看一个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原理深入解析