首先,我们使用 MyBatis 提供的工具类 Resources 加载配置文件,得到一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的build
方法构建 SqlSessionFactory 对象。所以这里的 build 方法是我们分析配置文件解析过程的入口方法。那下面我们来看一下这个方法的代码:
// -☆- SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
// 调用重载方法
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建配置文件解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 调用 parse 方法解析配置文件,生成 Configuration 对象
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
// 创建 DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
从上面的代码中,我们大致可以猜出 MyBatis 配置文件是通过XMLConfigBuilder
进行解析的。不过目前这里还没有非常明确的解析逻辑,所以我们继续往下看。这次来看一下 XMLConfigBuilder 的parse
方法,如下:
// -☆- XMLConfigBuilder
public Configuration parse() {
if (parsed) {
throw new BuilderException(“Each XMLConfigBuilder can only be used once.”);
}
parsed = true;
// 解析配置
parseConfiguration(parser.evalNode(“/configuration”));
return configuration;
}
到这里大家可以看到一些端倪了,注意一个 xpath 表达式 - /configuration
。这个表达式代表的是 MyBatis 的<configuration/>
标签,这里选中这个标签,并传递给parseConfiguration
方法。我们继续跟下去。
private void parseConfiguration(XNode root) {
try {
// 解析 properties 配置
propertiesElement(root.evalNode(“properties”));
// 解析 settings 配置,并将其转换为 Properties 对象
Properties settings = settingsAsProperties(root.evalNode(“settings”));
// 加载 vfs
loadCustomVfs(settings);
// 解析 typeAliases 配置
typeAliasesElement(root.evalNode(“typeAliases”));
// 解析 plugins 配置
pluginElement(root.evalNode(“plugins”));
// 解析 objectFactory 配置
objectFactoryElement(root.evalNode(“objectFactory”));
// 解析 objectWrapperFactory 配置
objectWrapperFactoryElement(root.evalNode(“objectWrapperFactory”));
// 解析 reflectorFactory 配置
reflectorFactoryElement(root.evalNode(“reflectorFactory”));
// settings 中的信息设置到 Configuration 对象中
settingsElement(settings);
// 解析 environments 配置
environmentsElement(root.evalNode(“environments”));
// 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
databaseIdProviderElement(root.evalNode(“databaseIdProvider”));
// 解析 typeHandlers 配置
typeHandlerElement(root.evalNode(“typeHandlers”));
// 解析 mappers 配置
mapperElement(root.evalNode(“mappers”));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
到此,一个 MyBatis 的解析过程就出来了,每个配置的解析逻辑都封装在了相应的方法中。在下面分析过程中,我不打算按照方法调用的顺序进行分析,我会适当进行一定的调整。同时,MyBatis 中配置较多,对于一些不常用的配置,这里会略过。那下面我们开始进行分析吧。
2.2 解析 properties 配置
解析properties
节点是由propertiesElement
这个方法完成的,该方法的逻辑比较简单。在分析方法源码前,先来看一下 properties 节点的配置内容。如下:
在上面的配置中,我为 properties 节点配置了一个 resource 属性,以及两个子节点。下面我们参照上面的配置,来分析一下 propertiesElement 的逻辑。相关分析如下。
// -☆- XMLConfigBuilder
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
Properties defaults = context.getChildrenAsProperties();
// 获取 propertis 节点中的 resource 和 url 属性值
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));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 将属性值设置到 configuration 中
configuration.setVariables(defaults);
}
}
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
// 获取并遍历子节点
for (XNode child : getChildren()) {
// 获取 property 节点的 name 和 value 属性
String name = child.getStringAttribute(“name”);
String value = child.getStringAttribute(“value”);
if (name != null && value != null) {
// 设置属性到属性对象中
properties.setProperty(name, value);
}
}
return properties;
}
// -☆- XNode
public List getChildren() {
List children = new ArrayList();
// 获取子节点列表
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
// 将节点对象封装到 XNode 中,并将 XNode 对象放入 children 列表中
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
上面是 properties 节点解析的主要过程,不是很复杂。主要包含三个步骤,一是解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。二是从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。第二步对应的代码比较简单,这里就不分析了。有兴趣的话,大家可以自己去看看。最后一步则是将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。
需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。比如上面配置中的jdbc.properties
内容如下:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/myblog?..
jdbc.username=root
jdbc.password=1234
与 properties 子节点内容合并后,结果如下:
如上,原jdbc.username
值为coolblog
,现在被覆盖为了root
。同名属性覆盖的问题需要大家注意一下,其他的就没什么了,继续往下分析。
2.3 解析 settings 配置
2.3.1 settings 节点的解析过程
settings 相关配置是 MyBatis 中非常重要的配置,这些配置用于调整 MyBatis 运行时的行为。settings 配置繁多,在对这些配置不熟悉的情况下,保持默认配置即可。关于 settings 相关配置,MyBatis 官网上进行了比较详细的描述,大家可以去了解一下。在本节中,暂时还用不到这些配置,所以即使不了解这些配置也没什么关系。下面先来看一个比较简单的配置,如下:
接下来,对照上面的配置,来分析源码。如下:
// -☆- XMLConfigBuilder
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 获取 settings 子节点中的内容,getChildrenAsProperties 方法前面已分析过,这里不再赘述
Properties props = context.getChildrenAsProperties();
// 创建 Configuration 类的“元信息”对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
// 检测 Configuration 中是否存在相关属性,不存在则抛出异常
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException(“The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).”);
}
}
return props;
}
如上,settingsAsProperties 方法看起来并不复杂,不过这是一个假象。在上面的代码中出现了一个陌生的类MetaClass
,这个类是用来做什么的呢?答案是用来解析目标类的一些元信息,比如类的成员变量,getter/setter 方法等。关于这个类的逻辑,待会我会详细解析。接下来,简单总结一下上面代码的逻辑。如下:
-
解析 settings 子节点的内容,并将解析结果转成 Properties 对象
-
为 Configuration 创建
元信息
对象 -
通过 MetaClass 检测 Configuration 中是否存在某个属性的 setter 方法,不存在则抛异常
-
若通过 MetaClass 的检测,则返回 Properties 对象,方法逻辑结束
下面,我们来重点关注一下第2步和第3步的流程。这两步流程对应的代码较为复杂,需要一点耐心阅读。好了,下面开始分析。
2.3.2 元信息对象创建过程
元信息类MetaClass
的构造方法为私有类型,所以不能直接创建,必须使用其提供的forClass
方法进行创建。它的创建逻辑如下:
public class MetaClass {
private final ReflectorFactory reflectorFactory;
private final Reflector reflector;
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
// 根据类型创建 Reflector
this.reflector = reflectorFactory.findForClass(type);
}
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
// 调用构造方法
return new MetaClass(type, reflectorFactory);
}
// 省略其他方法
}
上面的代码看起来很简单,不过这只是冰山一角。上面代码出现了两个新的类ReflectorFactory
和Reflector
,MetaClass 通过引入这些新类帮助它完成功能。下面我们看一下hasSetter
方法的源码就知道是怎么回事了。
// -☆- MetaClass
public boolean hasSetter(String name) {
// 属性分词器,用于解析属性名
PropertyTokenizer prop = new PropertyTokenizer(name);
// hasNext 返回 true,则表明 name 是一个复合属性,后面会进行分析
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 {
// 调用 reflector 的 hasSetter 方法
return reflector.hasSetter(prop.getName());
}
}
从上面的代码中,我们可以看出 MetaClass 中的 hasSetter 方法最终调用了 Reflector 的 hasSetter 方法。关于 Reflector 的 hasSetter 方法,这里先不分析,Reflector 这个类的逻辑较为复杂,本节会在随后进行详细说明。下面来简单介绍一下上面代码中出现的几个类:
-
ReflectorFactory -> 顾名思义,Reflector 的工厂类,兼有缓存 Reflector 对象的功能
-
Reflector -> 反射器,用于解析和存储目标类中的元信息
-
PropertyTokenizer -> 属性名分词器,用于处理较为复杂的属性名
上面的描述比较简单,仅从上面的描述中,还不能让大家有更深入的理解。所以下面单独分析一下这几个类的逻辑,首先是ReflectorFactory
。ReflectorFactory
是一个接口,MyBatis 中目前只有一个实现类DefaultReflectorFactory
,它的分析如下:
2.3.2.1 DefaultReflectorFactory 源码分析
DefaultReflectorFactory 用于创建 Reflector,同时兼有缓存的功能,它的源码如下。
public class DefaultReflectorFactory implements ReflectorFactory {
private boolean classCacheEnabled = true;
/** 目标类和反射器映射缓存 */
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap
// 省略部分代码
@Override
public Reflector findForClass(Class<?> type) {
// classCacheEnabled 默认为 true
if (classCacheEnabled) {
// 从缓存中获取 Reflector 对象
Reflector cached = reflectorMap.get(type);
// 缓存为空,则创建一个新的 Reflector 实例,并放入缓存中
if (cached == null) {
cached = new Reflector(type);
// 将 <type, cached> 映射缓存到 map 中,方便下次取用
reflectorMap.put(type, cached);
}
return cached;
} else {
// 创建一个新的 Reflector 实例
return new Reflector(type);
}
}
}
如上,DefaultReflectorFactory 的findForClass
方法逻辑不是很复杂,包含两个访存操作,和一个对象创建操作。代码注释的比较清楚了,就不多说了。接下来,来分析一下反射器 Reflector。
2.3.2.2 Reflector 源码分析
本小节,我们来看一下 Reflector 的源码。Reflector 这个类的用途主要是是通过反射获取目标类的 getter 方法及其返回值类型,setter 方法及其参数值类型等元信息。并将获取到的元信息缓存到相应的集合中,供后续使用。Reflector 本身代码比较多,这里不能一一分析。本小节,我将会分析三部分逻辑,分别如下:
-
Reflector 构造方法及成员变量分析
-
getter 方法解析过程
-
setter 方法解析过程
下面我们按照这个步骤进行分析,先来分析 Reflector 构造方法。
● Reflector 构造方法及成员变量分析
Reflector 构造方法中包含了很多初始化逻辑,目标类的元信息解析过程也是在构造方法中完成的,这些元信息最终会被保存到 Reflector 的成员变量中。下面我们先来看看 Reflector 的构造方法和相关的成员变量定义,代码如下:
public class Reflector {
private final Class<?> type;
private final String[] readablePropertyNames;
private final String[] writeablePropertyNames;
private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
private final Map<String, Class<?>> setTypes = new HashMap
private final Map<String, Class<?>> getTypes = new HashMap
private Constructor<?> defaultConstructor;
private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
public 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 的构造方法看起来略为复杂,不过好在一些比较复杂的逻辑都封装在了相应的方法中,这样整体的逻辑就比较清晰了。Reflector 构造方法所做的事情均已进行了注释,大家对照着注释先看一下。相关方法的细节待会会进行分析。看完构造方法,下面我来通过表格的形式,列举一下 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
方法。该方法的源码分析如下:
private void addGetMethods(Class<?> cls) {
Map<String, List> conflictingGetters = new HashMap<String, List>();
// 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了
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)
|| (name.startsWith(“is”) && name.length() > 2)) {
// 将 getXXX 或 isXXX 等方法名转成相应的属性,比如 getName -> name
name = PropertyNamer.methodToProperty(name);
/*
-
将冲突的方法添加到 conflictingGetters 中。考虑这样一种情况:
-
getTitle 和 isTitle 两个方法经过 methodToProperty 处理,
-
均得到 name = title,这会导致冲突。
-
对于冲突的方法,这里先统一起存起来,后续再解决冲突
*/
addMethodConflict(conflictingGetters, name, method);
}
}
// 解决 getter 冲突
resolveGetterConflicts(conflictingGetters);
}
如上,addGetMethods 方法的执行流程如下:
-
获取当前类,接口,以及父类中的方法
-
遍历上一步获取的方法数组,并过滤出以
get
和is
开头的方法 -
将方法名转换成相应的属性名
-
将属性名和方法对象添加到冲突集合中
-
解决冲突
在上面的执行流程中,前三步比较简单,大家自行分析吧。第4步也不复杂,下面我会把源码贴出来,大家看一下就能懂。在这几步中,第5步逻辑比较复杂,这一步逻辑我们重点关注一下。下面继续看源码吧。
/** 添加属性名和方法对象到冲突集合中 */
private void addMethodConflict(Map<String, List> conflictingMethods, String name, Method method) {
List list = conflictingMethods.get(name);
if (list == null) {
list = new ArrayList();
conflictingMethods.put(name, list);
}
list.add(method);
}
/** 解决冲突 */
private void resolveGetterConflicts(Map<String, List> conflictingGetters) {
for (Entry<String, List> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
// 获取返回值类型
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
/*
-
两个方法的返回值类型一致,若两个方法返回值类型均为 boolean,则选取 isXXX 方法
-
为 winner。否则无法决定哪个方法更为合适,只能抛出异常
*/
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
-
propName + " in class " + winner.getDeclaringClass()
-
“. This breaks the JavaBeans specification and can cause unpredictable results.”);
/*
-
如果方法返回值类型为 boolean,且方法名以 “is” 开头,
-
则认为候选方法 candidate 更为合适
*/
} else if (candidate.getName().startsWith(“is”)) {
winner = candidate;
}
/*
-
winnerType 是 candidateType 的子类,类型上更为具体,
-
则认为当前的 winner 仍是合适的,无需做什么事情
*/
} else if (candidateType.isAssignableFrom(winnerType)) {
/*
-
candidateType 是 winnerType 的子类,此时认为 candidate 方法更为合适,
-
故将 winner 更新为 candidate
*/
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
-
propName + " in class " + winner.getDeclaringClass()
-
“. This breaks the JavaBeans specification and can cause unpredictable results.”);
}
}
// 将筛选出的方法添加到 getMethods 中,并将方法返回值添加到 getTypes 中
addGetMethod(propName, winner);
}
}
private void addGetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
getMethods.put(name, new MethodInvoker(method));
// 解析返回值类型
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
// 将返回值类型由 Type 转为 Class,并将转换后的结果缓存到 setTypes 中
getTypes.put(name, typeToClass(returnType));
}
}
以上就是解除冲突的过程,代码有点长,不太容易看懂。这里大家只要记住解决冲突的规则即可理解上面代码的逻辑。相关规则如下:
-
冲突方法的返回值类型具有继承关系,子类返回值对应的方法被认为是更合适的选择
-
冲突方法的返回值类型相同,如果返回值类型为
boolean
,那么以is
开头的方法则是更合适的方法 -
冲突方法的返回值类型相同,但返回值类型非
boolean
,此时出现歧义,抛出异常 -
冲突方法的返回值类型不相关,无法确定哪个是更好的选择,此时直接抛异常
分析完 getter 方法的解析过程,下面继续分析 setter 方法的解析过程。
● setter 方法解析过程
与 getter 方法解析过程相比,setter 方法的解析过程与此有一定的区别。主要体现在冲突出现的原因,以及冲突的解决方法上。那下面,我们深入源码来找出两者之间的区别。
private void addSetMethods(Class<?> cls) {
Map<String, List> conflictingSetters = new HashMap<String, List>();
// 获取当前类,接口,以及父类中的方法。该方法逻辑不是很复杂,这里就不展开了
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);
}
从上面的代码和注释中,我们可知道 setter 方法之间出现冲突的原因。即方法存在重载,方法重载导致methodToProperty
方法解析出的属性名完全一致。而 getter 方法之间出现冲突的原因是getXXX
和isXXX
对应的属性名一致。既然冲突发生了,要进行调停,那接下来继续来看看调停冲突的逻辑。
private void resolveSetterConflicts(Map<String, List> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List setters = conflictingSetters.get(propName);
/*
-
获取 getter 方法的返回值类型,由于 getter 方法不存在重载的情况,
-
所以可以用它的返回值类型反推哪个 setter 的更为合适
*/
Class<?> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
for (Method setter : setters) {
// 获取参数类型
Class<?> paramType = setter.getParameterTypes()[0];
if (paramType.equals(getterType)) {
// 参数类型和返回类型一致,则认为是最好的选择,并结束循环
match = setter;
break;
}
if (exception == null) {
try {
// 选择一个更为合适的方法
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
match = null;
exception = e;
}
}
}
// 若 match 为空,表示没找到更为合适的方法,此时抛出异常
if (match == null) {
throw exception;
} else {
// 将筛选出的方法放入 setMethods 中,并将方法参数值添加到 setTypes 中
addSetMethod(propName, match);
}
}
}
/** 从两个 setter 方法中选择一个更为合适方法 */
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
if (setter1 == null) {
return setter2;
}
Class<?> paramType1 = setter1.getParameterTypes()[0];
Class<?> paramType2 = setter2.getParameterTypes()[0];
// 如果参数2可赋值给参数1,即参数2是参数1的子类,则认为参数2对应的 setter 方法更为合适
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
// 这里和上面情况相反
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
// 两种参数类型不相关,这里抛出异常
throw new ReflectionException(“Ambiguous setters defined for property '” + property + “’ in class '”
-
setter2.getDeclaringClass() + “’ with types '” + paramType1.getName() + “’ and '”
-
paramType2.getName() + “'.”);
}
private void addSetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
setMethods.put(name, new MethodInvoker(method));
// 解析参数类型列表
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
// 将参数类型由 Type 转为 Class,并将转换后的结果缓存到 setTypes
setTypes.put(name, typeToClass(paramTypes[0]));
}
}
关于 setter 方法冲突的解析规则,这里也总结一下吧。如下:
-
冲突方法的参数类型与 getter 的返回类型一致,则认为是最好的选择
-
冲突方法的参数类型具有继承关系,子类参数对应的方法被认为是更合适的选择
-
冲突方法的参数类型不相关,无法确定哪个是更好的选择,此时直接抛异常
到此关于 setter 方法的解析过程就说完了。我在前面说过 MetaClass 的hasSetter
最终调用了 Refactor 的hasSetter
方法,那么现在是时候分析 Refactor 的hasSetter
方法了。代码如下如下:
public boolean hasSetter(String propertyName) {
return setMethods.keySet().contains(propertyName);
}
代码如上,就两行,很简单,就不多说了。
2.3.2.3 PropertyTokenizer 源码分析
对于较为复杂的属性,需要进行进一步解析才能使用。那什么样的属性是复杂属性呢?来看个测试代码就知道了。
public class MetaClassTest {
private class Author {
private Integer id;
private String name;
private Integer age;
/** 一个作者对应多篇文章 */
private Article[] articles;
// 省略 getter/setter
}
private class Article {
private Integer id;
private String title;
private String content;
/** 一篇文章对应一个作者 */
private Author author;
// 省略 getter/setter
}
@Test
public void testHasSetter() {
// 为 Author 创建元信息对象
MetaClass authorMeta = MetaClass.forClass(Author.class, new DefaultReflectorFactory());
System.out.println(“------------☆ Author ☆------------”);
System.out.println("id -> " + authorMeta.hasSetter(“id”));
System.out.println("name -> " + authorMeta.hasSetter(“name”));
System.out.println("age -> " + authorMeta.hasSetter(“age”));
// 检测 Author 中是否包含 Article[] 的 setter
System.out.println("articles -> " + authorMeta.hasSetter(“articles”));
System.out.println("articles[] -> " + authorMeta.hasSetter(“articles[]”));
System.out.println("title -> " + authorMeta.hasSetter(“title”));
// 为 Article 创建元信息对象
MetaClass articleMeta = MetaClass.forClass(Article.class, new DefaultReflectorFactory());
System.out.println(“\n------------☆ Article ☆------------”);
System.out.println("id -> " + articleMeta.hasSetter(“id”));
System.out.println("title -> " + articleMeta.hasSetter(“title”));
System.out.println("content -> " + articleMeta.hasSetter(“content”));
// 下面两个均为复杂属性,分别检测 Article 类中的 Author 类是否包含 id 和 name 的 setter 方法
System.out.println("author.id -> " + articleMeta.hasSetter(“author.id”));
System.out.println("author.name -> " + articleMeta.hasSetter(“author.name”));
}
}
如上,Article
类中包含了一个Author
引用。然后我们调用 articleMeta 的 hasSetter 检测author.id
和author.name
属性是否存在,我们的期望结果为 true。测试结果如下:
如上,标记⑤处的输出均为 true,我们的预期达到了。标记②处检测 Article 数组的是否存在 setter 方法,结果也均为 true。这说明 PropertyTokenizer 对数组和复合属性均进行了处理。那它是如何处理的呢?答案如下:
public class PropertyTokenizer implements Iterator {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/6267ba9c40d6309c6d04508c1d0a9119.jpeg)
面试资料整理汇总
这些面试题是我朋友进阿里前狂刷七遍以上的面试资料,由于面试文档很多,内容更多,没有办法一一为大家展示出来,所以只好为大家节选出来了一部分供大家参考。
面试的本质不是考试,而是告诉面试官你会做什么,所以,这些面试资料中提到的技术也是要学会的,不然稍微改动一下你就凉凉了
在这里祝大家能够拿到心仪的offer!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
“author.name”));
}
}
如上,Article
类中包含了一个Author
引用。然后我们调用 articleMeta 的 hasSetter 检测author.id
和author.name
属性是否存在,我们的期望结果为 true。测试结果如下:
如上,标记⑤处的输出均为 true,我们的预期达到了。标记②处检测 Article 数组的是否存在 setter 方法,结果也均为 true。这说明 PropertyTokenizer 对数组和复合属性均进行了处理。那它是如何处理的呢?答案如下:
public class PropertyTokenizer implements Iterator {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-s1Bv4K1s-1711794288849)]
[外链图片转存中…(img-5pV6zyVn-1711794288850)]
[外链图片转存中…(img-Mb7HyWWD-1711794288850)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/6267ba9c40d6309c6d04508c1d0a9119.jpeg)
面试资料整理汇总
[外链图片转存中…(img-HyUqb0vT-1711794288850)]
[外链图片转存中…(img-N0rEYKFk-1711794288851)]
这些面试题是我朋友进阿里前狂刷七遍以上的面试资料,由于面试文档很多,内容更多,没有办法一一为大家展示出来,所以只好为大家节选出来了一部分供大家参考。
面试的本质不是考试,而是告诉面试官你会做什么,所以,这些面试资料中提到的技术也是要学会的,不然稍微改动一下你就凉凉了
在这里祝大家能够拿到心仪的offer!
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!