Mybatis源码分析(第一章)------配置文件的解析(1)
注:文章大部分内容转载自 https://www.tianxiaobo.com/ 以及 《Mybatis技术内幕》
1.1 解析配置文件的入口
在只使用Mybatis访问数据库时我们可以这样构建SqlSessionFactory对象:
使用MyBatis 提供的工具类 Resources 加载配置文件,得到一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的build()方法构建 SqlSessionFactory 对象。
1 String resource = "mybatis-config.xml"; 2 InputStream inputStream = Resources.getResourceAsStream(resource); 3 //将mybatis-config.xml作为一个输入流传入给build()方法 4 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
而在日常结合Spring使用Mybatis时配置文件中(applicationContext.xml)一般有如下配置信息:
1 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 2 <property name="dataSource" ref="dataSource"></property> 3 <property name="mapperLocations" value="classpath:com/emiyaa/mappers/*.xml"></property> 4 </bean>
我们要分析的build()方法的进入路径是:SqlSessionFactoryBean → SqlSessionBuilder → build()
1 public SqlSessionFactory build(InputStream inputStream) { 2 return this.build((InputStream) inputStream, (String) null, (Properties) null); 3 } 4 5 public SqlSessionFactory build(InputStream inputStream, String environment) { 6 return this.build((InputStream) inputStream, environment, (Properties) null); 7 } 8 9 public SqlSessionFactory build(InputStream inputStream, Properties properties) { 10 return this.build((InputStream) inputStream, (String) null, properties); 11 } 12 13 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { 14 SqlSessionFactory var5; 15 try { 16 //创建配置文件解析器,将配置文件加载为XMLConfig 17 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 18 //构建SqlSessionFactory,调用 parse 方法解析配置文件,生成 Configuration 对象 19 var5 = this.build(parser.parse()); 20 throw ExceptionFactory.wrapException("Error building SqlSession.", var14); 21 } finally { 22 ErrorContext.instance().reset(); 23 24 try { 25 inputStream.close(); 26 } catch (IOException var13) { 27 } 28 29 } 30 31 return var5; 32 } 33 34 public SqlSessionFactory build(Configuration config) { 35 //创建DefaultSqlSessionFactory 36 return new DefaultSqlSessionFactory(config); 37 }
可大致了解到XMLConfigBuilder有解析配置文件的功能,下面分析parse()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → parse()
1 public Configuration parse() { 2 if (this.parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } else { 5 this.parsed = true; 6 // 解析配置,xpath表达式 /configuration 代表Mybatis的<configuration/>标签 7 this.parseConfiguration(this.parser.evalNode("/configuration")); 8 return this.configuration; 9 } 10 }
进入parseConfiguration()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → parse() → parseConfiguration()
1 private void parseConfiguration(XNode root) { 2 try { 3 Properties settings = this.settingsAsPropertiess(root.evalNode("settings")); 4 this.propertiesElement(root.evalNode("properties")); 5 this.loadCustomVfs(settings); 6 this.typeAliasesElement(root.evalNode("typeAliases")); 7 this.pluginElement(root.evalNode("plugins")); 8 this.objectFactoryElement(root.evalNode("objectFactory")); 9 this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 10 this.reflectionFactoryElement(root.evalNode("reflectionFactory")); 11 this.settingsElement(settings); 12 this.environmentsElement(root.evalNode("environments")); 13 this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); 14 this.typeHandlerElement(root.evalNode("typeHandlers")); 15 this.mapperElement(root.evalNode("mappers")); 16 } catch (Exception var3) { 17 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); 18 } 19 }
发现方法中有很多配置的解析逻辑,下面开始分析其中重要的解析。
1.2 解析properties配置
1 <properties resource="prop.properties"><!-- 也可以配置url,但url和resource只能存在一个 --> 2 <property name="username" value="Emiyaa"/> 3 <property name="password" value="Emiyaa"/> 4 <property name="test" value="test"/> 5 </properties>
解析properties节点是由propertiesElement这个方法完成的,参照上面的配置,进入propertiesElement()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → propertiesElement()
1 private void propertiesElement(XNode context) throws Exception { 2 if (context != null) { 3 // 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties 4 Properties defaults = context.getChildrenAsProperties(); 5 // 获取 propertis 节点中的 resource 和 url 属性值 6 String resource = context.getStringAttribute("resource"); 7 String url = context.getStringAttribute("url"); 8 9 // 两者都不用空,则抛出异常 10 if (resource != null && url != null) { 11 throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); 12 } 13 if (resource != null) { 14 // 从文件系统中加载并解析属性文件 15 defaults.putAll(Resources.getResourceAsProperties(resource)); 16 } else if (url != null) { 17 // 通过 url 加载并解析属性文件 18 defaults.putAll(Resources.getUrlAsProperties(url)); 19 } 20 Properties vars = configuration.getVariables(); 21 if (vars != null) { 22 defaults.putAll(vars); 23 } 24 parser.setVariables(defaults); 25 // 将属性值设置到 configuration 中 26 configuration.setVariables(defaults); 27 } 28 } 29 30 public Properties getChildrenAsProperties() { 31 Properties properties = new Properties(); 32 // 获取并遍历子节点 33 for (XNode child : getChildren()) { 34 // 获取 property 节点的 name 和 value 属性 35 String name = child.getStringAttribute("name"); 36 String value = child.getStringAttribute("value"); 37 if (name != null && value != null) { 38 // 设置属性到属性对象中 39 properties.setProperty(name, value); 40 } 41 } 42 return properties; 43 } 44 45 public List<XNode> getChildren() { 46 List<XNode> children = new ArrayList<XNode>(); 47 // 获取子节点列表 48 NodeList nodeList = node.getChildNodes(); 49 if (nodeList != null) { 50 for (int i = 0, n = nodeList.getLength(); i < n; i++) { 51 Node node = nodeList.item(i); 52 if (node.getNodeType() == Node.ELEMENT_NODE) { 53 // 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中 54 children.add(new XNode(xpathParser, node, variables)); 55 } 56 } 57 } 58 return children; 59 }
节点解析过程可大致分为以下三步:
- 解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。
- 从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
- 将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。
※需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。
1.3 解析settings配置
1.3.1 setting节点的解析过程
cacheEnabled
Globally enables or disables any caches configured in any mapper under this configuration.
描述:全局开启或关闭配置文件中所有 mapper 已经配置的缓存。
可选值:true|false
默认值:true
lazyLoadingEnabled
Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be superseded for an specific relation by using the fetchType attribute on it.
描述:全局开启或关闭延迟加载。开启时,所有关联对象都会延迟加载。特定关联关系中可以通过设置 fetchType 属性来取覆盖该项的开关状态。
可选值:true|false
默认值:false
aggressiveLazyLoading
When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded on demand (see also lazyLoadTriggerMethods).
描述:开启或关闭属性的延迟加载。开启时,任何方法调用都将加载该对象的所有延迟属性。关闭时,每个属性将按需加载(参考 lazyLoadTriggerMethods)。
可选值:true|false
默认值:false (true in ≤3.4.1)
multipleResultSetsEnabled
Allows or disallows multiple ResultSets to be returned from a single statement (compatible driver required).
描述:是否允许单个语句中返回多个结果集(需要兼容驱动)。
可选值:true|false
默认值:true
useColumnLabe
Uses the column label instead of the column name. Different drivers behave differently in this respect. Refer to the driver documentation, or test out both modes to determine how your driver behaves.
描述:使用列别名替代列名。不同驱动在这方面表现不同。具体可参考驱动文档,或者通过测试判断这两种模式下不同驱动的表现。
可选值:true|false
默认值:true
useGeneratedKeys
Allows JDBC support for generated keys. A compatible driver is required. This setting forces generated keys to be used if set to true, as some drivers deny compatibility but still work (e.g. Derby).
描述:是否允许 JDBC 支持主键自动生成。需要兼容驱动。若设置为 true,则强制使用主键自动生成,尽管一些驱动不兼容也可以正常工作(如 Derby)。
可选项:true|false
默认值:false
autoMappingBehavior
Specifies if and how MyBatis should automatically map columns to fields/properties.
NONE disables auto-mapping.
PARTIAL will only auto-map results with no nested result mappings defined inside.
FULL will auto-map result mappings of any complexity (containing nested or otherwise).
描述:指定 Mybatis 是否自动映射列到字段或属性。
NONE:不允许自动映射。
PARTIAL:只自动映射没有定义嵌套结果集映射的结果集。
FULL:自动映射任意复杂的结果集(无论是否嵌套)。
可选值:NONE, PARTIAL, FULL
默认值:PARTIAL
autoMappingUnknownColumnBehavior
Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
NONE: Do nothing.
WARNING: Output warning log (The log level of 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' must be set to WARN).
FAILING: Fail mapping (Throw SqlSessionException)。
描述:指定发现自动映射目标未知列(或未知属性类型)的行为。
NONE:什么也不做。
WARNING:输出警告日志( 'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志级别要设置为 WARN)。
FAILING:映射失败(抛出 SqlSessionException 异常)。
可选值:NONE, WARNING, FAILING
默认值:NONE
defaultExecutorType
Configures the default executor.
SIMPLE executor does nothing special.
REUSE executor reuses prepared statements.
BATCH executor reuses statements and batches updates.
描述:配置默认执行器。
SIMPLE:普通的执行器。
REUSE:重复执行预处理语句(prepared statements)。
BATCH:重复执行语句并执行批量更新。
可选值:SIMPLE REUSE BATCH
默认值:SIMPLE
defaultStatementTimeout
Sets the number of seconds the driver will wait for a response from the database.
描述:设置驱动等待数据库响应的超时秒数。
可选值:Any positive integer //任意正整数
默认值:Not Set (null)
defaultFetchSize
Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting.
描述:为驱动的结果集获取数量设置一个提示值。这个参数值可以被查询设置覆盖。
可选值:Any positive integer //任意正整数
默认值:Not Set (null)
safeRowBoundsEnabled
Allows using RowBounds on nested statements. If allow, set the false.
描述:允许在嵌套语句中使用 RowBound。如果允许,设置为 false。
可选值:true|false
默认值:false
safeResultHandlerEnabled
Allows using ResultHandler on nested statements. If allow, set the false.
描述:允许在嵌套语句中使用 ResultHandler。如果允许,设置为 false。
可选值:true|false
默认值:true
mapUnderscoreToCamelCase
Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn.
描述:开启驼峰规则命名的自动映射,即从经典数据库列名命名 A_COLUMN 到经典 Java 属性命名 aColumn 的自动映射。
可选值:true|false
默认值:False
localCacheScope
MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for statement execution, no data will be shared between two different calls to the same SqlSession.
描述:Mybatis 使用本地缓存机制(local cache)来防止循环引用和加速重复嵌套查询。
SESSION:默认设置,一个 session 期间的所有查询执行都将被缓存。
STATEMENT:本地 session 仅在语句执行时使用,且对同一个 session 的不同调用不会共享数据。
可选项:SESSION|STATEMENT
默认值:SESSION
jdbcTypeForNull
Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER.
描述:未对参数指定 JDBC 类型时,当参数为空值时指定 JDBC 类型。某些驱动要求指定列的 JDBC 类型,某些驱动用一般类型,比如 NULL、VARCHAR 或 OTHER 即可。
可选值:JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER
默认值:OTHER
lazyLoadTriggerMethods
Specifies which Object's methods trigger a lazy load
描述:指定对象的哪些方法触发一次延迟加载。
可选值:A method name list separated by commas //逗号分隔的方法名称列表
默认值:equals,clone,hashCode,toString
defaultScriptingLanguage
Specifies the language used by default for dynamic SQL generation.
描述:指定动态 SQL 生成的默认语言。
可选值:A type alias or fully qualified class name.
类型别名或完全限定类名
默认值:org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler
Specifies the TypeHandler used by default for Enum. (Since: 3.4.5)
描述:指定 Enum 默认使用的 TypeHandler(从3.4.5开始)。
可选值:A type alias or fully qualified class name. //类型别名或完全限定类名
默认值:org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls
Specifies if setters or map's put method will be called when a retrieved value is null. It is useful when you rely on Map.keySet() or null value initialization. Note primitives such as (int,boolean,etc.) will not be set to null.
描述:指定当结果集中值为空时是否调用映射对象的 setter 方法或 map 的 put 方法。如果你依赖于 Map.keySet() 或者在 null 值初始化时,该设置有用。注意,基本类型比如 int 、boolean 等不能被设置为 null。
可选值:true | false
默认值:false
returnInstanceForEmptyRow
MyBatis, by default, returns null when all the columns of a returned row are NULL. When this setting is enabled, MyBatis returns an empty instance instead. Note that it is also applied to nested results (i.e. collectioin and association). Since: 3.4.2
描述:当返回行的所有列都是 null 时,Mybatis 默认返回 null 。当该设置被开启时,Mybatis 会返回一个空实例。注意这将同样适用于嵌套结果集(如 collection 和 association)。从 3.4.2 开始。
可选值:true | false
默认值:false
logPrefix
Specifies the prefix string that MyBatis will add to the logger names.
描述:指定 Mybatis 添加到日志名称的前缀。
可选值:Any String
默认值:Not set
logImpl
Specifies which logging implementation MyBatis should use. If this setting is not present logging implementation will be autodiscovered.
描述:指定 Mybatis 使用的日志工具。如果此项未设置,将会自动查找日志工具。
可选值:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
默认值:Not set
proxyFactory
Specifies the proxy tool that MyBatis will use for creating lazy loading capable objects.
描述:指定 Mybatis 创建具有懒加载能力的对象的代理工具。
可选值:CGLIB | JAVASSIST
默认值:JAVASSIST (MyBatis 3.3 or above)
vfsImpl
Specifies VFS implementations
描述:指定 VFS 的具体实现。
可选值:Fully qualified class names of custom VFS implementation separated by commas. //逗号分隔的自定义 VFS 具体实现的完全限定类名。
默认值:Not set
useActualParamName
Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1)
描述:允许将方法签名中定义的真实名称作为语句参数名称。为了使用这一特性,你的项目必须采用 java 8 编译,且必须加上 -parameters 选项。(从 3.4.1 开始)
可选值:true | false
默认值:true
configurationFactory
Specifies the class that provides an instance of Configuration. The returned Configuration instance is used to load lazy properties of deserialized objects. This class must have a method with a signature static Configuration getConfiguration(). (Since: 3.2.3)
描述:指定提供 Configuration 实例的类。返回的 Configuration 实例用来加载被反序列化对象的懒加载属性。这个类必须有一个 static Configuration getConfiguration() 签名方法。(从 3.2.3 开始)
可选值:A type alias or fully qualified class name. //类型别名或完全限定类名
默认值:Not set
1 <settings> 2 <setting name="cacheEnabled" value="true"/> 3 <setting name="lazyLoadingEnabled" value="true"/> 4 <setting name="multipleResultSetsEnabled" value="true"/> 5 <setting name="useColumnLabel" value="true"/> 6 <setting name="useGeneratedKeys" value="false"/> 7 <setting name="autoMappingBehavior" value="PARTIAL"/> 8 <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> 9 <setting name="defaultExecutorType" value="SIMPLE"/> 10 <setting name="defaultStatementTimeout" value="25"/> 11 <setting name="defaultFetchSize" value="100"/> 12 <setting name="safeRowBoundsEnabled" value="false"/> 13 <setting name="mapUnderscoreToCamelCase" value="false"/> 14 <setting name="localCacheScope" value="SESSION"/> 15 <setting name="jdbcTypeForNull" value="OTHER"/> 16 <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> 17 </settings>
关于Mybatis的settings配置我找了一个较全版本的配置(https://www.jianshu.com/p/3883ea8ad3a6),下面开始分析settingsAsProperties()方法:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder →settingsAsProperties()
1 private Properties settingsAsProperties(XNode context) { 2 if (context == null) { 3 return new Properties(); 4 } 5 // 获取 settings 子节点中的内容,getChildrenAsProperties 方法前面已分析过,这里不再赘述 6 Properties props = context.getChildrenAsProperties(); 7 8 // 创建 Configuration 类的“元信息”对象 9 MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); 10 for (Object key : props.keySet()) { 11 // 检测 Configuration 中是否存在相关属性,不存在则抛出异常 12 if (!metaConfig.hasSetter(String.valueOf(key))) { 13 throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); 14 } 15 } 16 return props; 17 }
以上代码的逻辑大致为:
- 解析 settings 子节点的内容,并将解析结果转成 Properties 对象
- 为 Configuration 创建元信息对象
- 通过 MetaClass 检测 Configuration 中是否存在某个属性的 setter 方法,不存在则抛异常
- 若通过 MetaClass 的检测,则返回 Properties 对象,方法逻辑结束
之后进行第二步与第三步的分析,以及MetaClass类的分析:
1.3.2 元信息对象创建过程
MetaClass为元信息类,由于其构造方法私有,所以由其本身提供的forClass方法创建对象:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass
1 public class MetaClass { 2 private final ReflectorFactory reflectorFactory; 3 private final Reflector reflector; 4 5 private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) { 6 this.reflectorFactory = reflectorFactory; 7 // 根据类型创建 Reflector 8 this.reflector = reflectorFactory.findForClass(type); 9 } 10 11 public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) { 12 // 调用构造方法 13 return new MetaClass(type, reflectorFactory); 14 } 15 16 // 省略其他方法 17 }
以上代码出现了两个新类ReflectorFactory以及Reflector,二者被MetaClass引入为其完成功能,现在来看hasSetter()方法了解一下:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → hasSetter()
1 public boolean hasSetter(String name) { 2 // 属性分词器,用于解析属性名 3 PropertyTokenizer prop = new PropertyTokenizer(name); 4 // hasNext 返回 true,则表明 name 是一个复合属性,后面会进行分析 5 if (prop.hasNext()) { 6 // 调用 reflector 的 hasSetter 方法 7 if (reflector.hasSetter(prop.getName())) { 8 // 为属性创建创建 MetaClass 9 MetaClass metaProp = metaClassForProperty(prop.getName()); 10 // 再次调用 hasSetter 11 return metaProp.hasSetter(prop.getChildren()); 12 } else { 13 return false; 14 } 15 } else { 16 // 调用 reflector 的 hasSetter 方法 17 return reflector.hasSetter(prop.getName()); 18 } 19 }
MetaClass 中的 hasSetter 方法最终调用了 Reflector 的 hasSetter 方法,上面代码出现了下面几个类:
- ReflectorFactory -> 顾名思义,Reflector 的工厂类,兼有缓存 Reflector 对象的功能
- Reflector -> 反射器,用于解析和存储目标类中的元信息
- PropertyTokenizer -> 属性名分词器,用于处理较为复杂的属性名
1.3.2.1 defaultReflectorFactory源码分析
ReflectorFactory 是一个接口,MyBatis 中目前只有一个实现类DefaultReflectorFactory,源码分析如下:org.apache.ibatis.reflection
1 public class DefaultReflectorFactory implements ReflectorFactory { 2 3 private boolean classCacheEnabled = true; 4 /** 目标类和反射器映射缓存 */ 5 private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>(); 6 7 // 省略部分代码 8 9 @Override 10 public Reflector findForClass(Class<?> type) { 11 // classCacheEnabled 默认为 true 12 if (classCacheEnabled) { 13 // 从缓存中获取 Reflector 对象 14 Reflector cached = reflectorMap.get(type); 15 // 缓存为空,则创建一个新的 Reflector 实例,并放入缓存中 16 if (cached == null) { 17 cached = new Reflector(type); 18 // 将 <type, cached> 映射缓存到 map 中,方便下次取用 19 reflectorMap.put(type, cached); 20 } 21 return cached; 22 } else { 23 // 创建一个新的 Reflector 实例 24 return new Reflector(type); 25 } 26 } 27 }
DefaultReflectorFactory 的findForClass方法逻辑包含两个访存操作,和一个对象创建操作。
1.3.2.2 Reflector源码分析
Reflector 构造方法中包含了很多初始化逻辑,目标类的元信息解析过程也是在构造方法中完成的,这些元信息最终会被保存到 Reflector 的成员变量中,以下Reflector的构造方法以及成员变量定义:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → Reflector
1 public class Reflector { 2 3 private final Class<?> type; 4 private final String[] readablePropertyNames; 5 private final String[] writeablePropertyNames; 6 private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>(); 7 private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>(); 8 private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>(); 9 private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>(); 10 private Constructor<?> defaultConstructor; 11 12 private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>(); 13 14 public Reflector(Class<?> clazz) { 15 type = clazz; 16 // 解析目标类的默认构造方法,并赋值给 defaultConstructor 变量 17 addDefaultConstructor(clazz); 18 19 // 解析 getter 方法,并将解析结果放入 getMethods 中 20 addGetMethods(clazz); 21 22 // 解析 setter 方法,并将解析结果放入 setMethods 中 23 addSetMethods(clazz); 24 25 // 解析属性字段,并将解析结果添加到 setMethods 或 getMethods 中 26 addFields(clazz); 27 28 // 从 getMethods 映射中获取可读属性名数组 29 readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]); 30 31 // 从 setMethods 映射中获取可写属性名数组 32 writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]); 33 34 // 将所有属性名的大写形式作为键,属性名作为值,存入到 caseInsensitivePropertyMap 中 35 for (String propName : readablePropertyNames) { 36 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); 37 } 38 for (String propName : writeablePropertyNames) { 39 caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName); 40 } 41 } 42 43 // 省略其他方法 44 }
下表列举了Reflector 部分成员变量的用途。
| 变量名 | 类型 | 用途 |
|---|---|---|
| readablePropertyNames | String[] | 可读属性名称数组,用于保存 getter 方法对应的属性名称 |
| writeablePropertyNames | String[] | 可写属性名称数组,用于保存 setter 方法对应的属性名称 |
| setMethods | Map<String, Invoker> | 用于保存属性名称到 Invoke 的映射。setter 方法会被封装到 MethodInvoker 对象中,Invoke 实现类比较简单,大家自行分析 |
| getMethods | Map<String, Invoker> | 用于保存属性名称到 Invoke 的映射。同上,getter 方法也会被封装到 MethodInvoker 对象中 |
| setTypes | Map<String, Class<?>> | 用于保存 setter 对应的属性名与参数类型的映射 |
| getTypes | Map<String, Class<?>> | 用于保存 getter 对应的属性名与返回值类型的映射 |
| caseInsensitivePropertyMap | Map<String, String> | 用于保存大写属性名与属性名之间的映射,比如 <NAME, name> |
上面列举了一些集合变量,这些变量用于缓存各种原信息。为了了解更多细节,下面开始分析一些源码:
-
getter方法解析过程
getter 方法解析的逻辑被封装在了addGetMethods方法中,这个方法除了会解析形如getXXX的方法,同时也会解析isXXX方法。该方法的源码分析如下:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → Reflector → addGetMethods()
1 private void addGetMethods(Class<?> cls) { 2 Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>(); 3 // 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了 4 Method[] methods = getClassMethods(cls); 5 for (Method method : methods) { 6 // getter 方法不应该有参数,若存在参数,则忽略当前方法 7 if (method.getParameterTypes().length > 0) { 8 continue; 9 } 10 String name = method.getName(); 11 // 过滤出以 get 或 is 开头的方法 12 if ((name.startsWith("get") && name.length() > 3) 13 || (name.startsWith("is") && name.length() > 2)) { 14 // 将 getXXX 或 isXXX 等方法名转成相应的属性,比如 getName -> name 15 name = PropertyNamer.methodToProperty(name); 16 /* 17 * 将冲突的方法添加到 conflictingGetters 中。考虑这样一种情况: 18 * 19 * getTitle 和 isTitle 两个方法经过 methodToProperty 处理, 20 * 均得到 name = title,这会导致冲突。 21 * 22 * 对于冲突的方法,这里先统一起存起来,后续再解决冲突 23 */ 24 addMethodConflict(conflictingGetters, name, method); 25 } 26 } 27 28 // 解决 getter 冲突 29 resolveGetterConflicts(conflictingGetters); 30 }
addGetMethods 方法的执行流程如下:
- 获取当前类,接口,以及父类中的方法
- 遍历上一步获取的方法数组,并过滤出以
get和is开头的方法 - 将方法名转换成相应的属性名
- 将属性名和方法对象添加到冲突集合中
下面来看第四步第五步的源码分析:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → Reflector → addGetMethods → addMethodConflict()/resolveGetterConflicts()
1 /** 添加属性名和方法对象到冲突集合中 */ 2 private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) { 3 List<Method> list = conflictingMethods.get(name); 4 if (list == null) { 5 list = new ArrayList<Method>(); 6 conflictingMethods.put(name, list); 7 } 8 list.add(method); 9 } 10 11 /** 解决冲突 */ 12 private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) { 13 for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) { 14 Method winner = null; 15 String propName = entry.getKey(); 16 for (Method candidate : entry.getValue()) { 17 if (winner == null) { 18 winner = candidate; 19 continue; 20 } 21 // 获取返回值类型 22 Class<?> winnerType = winner.getReturnType(); 23 Class<?> candidateType = candidate.getReturnType(); 24 25 /* 26 * 两个方法的返回值类型一致,若两个方法返回值类型均为 boolean,则选取 isXXX 方法 27 * 为 winner。否则无法决定哪个方法更为合适,只能抛出异常 28 */ 29 if (candidateType.equals(winnerType)) { 30 if (!boolean.class.equals(candidateType)) { 31 throw new ReflectionException( 32 "Illegal overloaded getter method with ambiguous type for property " 33 + propName + " in class " + winner.getDeclaringClass() 34 + ". This breaks the JavaBeans specification and can cause unpredictable results."); 35 36 /* 37 * 如果方法返回值类型为 boolean,且方法名以 "is" 开头, 38 * 则认为候选方法 candidate 更为合适 39 */ 40 } else if (candidate.getName().startsWith("is")) { 41 winner = candidate; 42 } 43 44 /* 45 * winnerType 是 candidateType 的子类,类型上更为具体, 46 * 则认为当前的 winner 仍是合适的,无需做什么事情 47 */ 48 } else if (candidateType.isAssignableFrom(winnerType)) { 49 50 /* 51 * candidateType 是 winnerType 的子类,此时认为 candidate 方法更为合适, 52 * 故将 winner 更新为 candidate 53 */ 54 } else if (winnerType.isAssignableFrom(candidateType)) { 55 winner = candidate; 56 } else { 57 throw new ReflectionException( 58 "Illegal overloaded getter method with ambiguous type for property " 59 + propName + " in class " + winner.getDeclaringClass() 60 + ". This breaks the JavaBeans specification and can cause unpredictable results."); 61 } 62 } 63 64 // 将筛选出的方法添加到 getMethods 中,并将方法返回值添加到 getTypes 中 65 addGetMethod(propName, winner); 66 } 67 } 68 69 private void addGetMethod(String name, Method method) { 70 if (isValidPropertyName(name)) { 71 getMethods.put(name, new MethodInvoker(method)); 72 // 解析返回值类型 73 Type returnType = TypeParameterResolver.resolveReturnType(method, type); 74 // 将返回值类型由 Type 转为 Class,并将转换后的结果缓存到 setTypes 中 75 getTypes.put(name, typeToClass(returnType)); 76 } 77 }
总结出解决冲突的规则:
- 冲突方法的返回值类型具有继承关系,子类返回值对应的方法被认为是更合适的选择
- 冲突方法的返回值类型相同,如果返回值类型为
boolean,那么以is开头的方法则是更合适的方法 - 冲突方法的返回值类型相同,但返回值类型非
boolean,此时出现歧义,抛出异常 - 冲突方法的返回值类型不相关,无法确定哪个是更好的选择,此时直接抛异常
- setter方法的解析过程
与 getter 方法解析过程相比,setter 方法的解析过程与此有一定的区别。主要体现在冲突出现的原因,以及冲突的解决方法上,下面来分析源码:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → Reflector → addSetMethods()
1 private void addSetMethods(Class<?> cls) { 2 Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>(); 3 // 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了 4 Method[] methods = getClassMethods(cls); 5 for (Method method : methods) { 6 String name = method.getName(); 7 // 过滤出 setter 方法,且方法仅有一个参数 8 if (name.startsWith("set") && name.length() > 3) { 9 if (method.getParameterTypes().length == 1) { 10 name = PropertyNamer.methodToProperty(name); 11 /* 12 * setter 方法发生冲突原因是:可能存在重载情况,比如: 13 * void setSex(int sex); 14 * void setSex(SexEnum sex); 15 */ 16 addMethodConflict(conflictingSetters, name, method); 17 } 18 } 19 } 20 // 解决 setter 冲突 21 resolveSetterConflicts(conflictingSetters); 22 }
我们可知道 setter 方法之间出现冲突的原因。即方法存在重载,方法重载导致methodToProperty方法解析出的属性名完全一致。而 getter 方法之间出现冲突的原因是getXXX和isXXX对应的属性名一致。
既然冲突发生了,要进行调停,那接下来继续来看看调停冲突的逻辑:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → Reflector → addSetMethods → resolveSetterConflicts()
1 private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) { 2 for (String propName : conflictingSetters.keySet()) { 3 List<Method> setters = conflictingSetters.get(propName); 4 /* 5 * 获取 getter 方法的返回值类型,由于 getter 方法不存在重载的情况, 6 * 所以可以用它的返回值类型反推哪个 setter 的更为合适 7 */ 8 Class<?> getterType = getTypes.get(propName); 9 Method match = null; 10 ReflectionException exception = null; 11 for (Method setter : setters) { 12 // 获取参数类型 13 Class<?> paramType = setter.getParameterTypes()[0]; 14 if (paramType.equals(getterType)) { 15 // 参数类型和返回类型一致,则认为是最好的选择,并结束循环 16 match = setter; 17 break; 18 } 19 if (exception == null) { 20 try { 21 // 选择一个更为合适的方法 22 match = pickBetterSetter(match, setter, propName); 23 } catch (ReflectionException e) { 24 match = null; 25 exception = e; 26 } 27 } 28 } 29 // 若 match 为空,表示没找到更为合适的方法,此时抛出异常 30 if (match == null) { 31 throw exception; 32 } else { 33 // 将筛选出的方法放入 setMethods 中,并将方法参数值添加到 setTypes 中 34 addSetMethod(propName, match); 35 } 36 } 37 } 38 39 /** 从两个 setter 方法中选择一个更为合适方法 */ 40 private Method pickBetterSetter(Method setter1, Method setter2, String property) { 41 if (setter1 == null) { 42 return setter2; 43 } 44 Class<?> paramType1 = setter1.getParameterTypes()[0]; 45 Class<?> paramType2 = setter2.getParameterTypes()[0]; 46 47 // 如果参数2可赋值给参数1,即参数2是参数1的子类,则认为参数2对应的 setter 方法更为合适 48 if (paramType1.isAssignableFrom(paramType2)) { 49 return setter2; 50 51 // 这里和上面情况相反 52 } else if (paramType2.isAssignableFrom(paramType1)) { 53 return setter1; 54 } 55 56 // 两种参数类型不相关,这里抛出异常 57 throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '" 58 + setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '" 59 + paramType2.getName() + "'."); 60 } 61 62 private void addSetMethod(String name, Method method) { 63 if (isValidPropertyName(name)) { 64 setMethods.put(name, new MethodInvoker(method)); 65 // 解析参数类型列表 66 Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type); 67 // 将参数类型由 Type 转为 Class,并将转换后的结果缓存到 setTypes 68 setTypes.put(name, typeToClass(paramTypes[0])); 69 } 70 }
关于 setter 方法冲突的解析规则如下:
- 冲突方法的参数类型与 getter 的返回类型一致,则认为是最好的选择
- 冲突方法的参数类型具有继承关系,子类参数对应的方法被认为是更合适的选择
- 冲突方法的参数类型不相关,无法确定哪个是更好的选择,此时直接抛异常
MetaClass 的hasSetter最终调用了 Refactor 的hasSetter方法,如下:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → Reflector → hasSetter()
1 public boolean hasSetter(String propertyName) { 2 return setMethods.keySet().contains(propertyName); 3 }
1.3.2.3 PropertyTokenizer源码分析
对于较为复杂的属性,需要进行进一步解析才能使用:SqlSessionFactoryBean → SqlSessionBuilder → XMLConfigBuilder → MetaClass → PropertyTokenizer
1 public class PropertyTokenizer implements Iterator<PropertyTokenizer> { 2 3 private String name; 4 private final String indexedName; 5 private String index; 6 private final String children; 7 8 public PropertyTokenizer(String fullname) { 9 // 检测传入的参数中是否包含字符 '.' 10 int delim = fullname.indexOf('.'); 11 if (delim > -1) { 12 /* 13 * 以点位为界,进行分割。比如: 14 * fullname = www.coolblog.xyz 15 * 16 * 以第一个点为分界符: 17 * name = www 18 * children = coolblog.xyz 19 */ 20 name = fullname.substring(0, delim); 21 children = fullname.substring(delim + 1); 22 } else { 23 // fullname 中不存在字符 '.' 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 42 // 获取分解符前面的内容,比如 fullname = articles[1],name = articles 43 name = name.substring(0, delim); 44 } 45 } 46 47 // 省略 getter 48 49 @Override 50 public boolean hasNext() { 51 return children != null; 52 } 53 54 @Override 55 public PropertyTokenizer next() { 56 // 对 children 进行再次切分,用于解析多重复合属性 57 return new PropertyTokenizer(children); 58 } 59 60 // 省略部分方法 61 }
篇幅原因后续部分写在下一篇文章里。

浙公网安备 33010602011771号