MyBatis(二)-反射机制
参考文献
- 深入剖析 MyBatis 核心原理 – 杨四正
Reflector
- Reflect是MyBatis反射模块的基础.要使用反射模块操作一个
Class
,都会先将Class
封装成一个Reflector
对象,在Reflector
中缓存Class
的元数据信息,这可以提高反射执行的效率.
核心初始化流程
-
既然是涉及反射操作,Reflector 必然要管理类的属性和方法,这些信息都记录在它的核心字段中,具体情况如下所示.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class Reflector {
private static final MethodHandle isRecordMethodHandle = getIsRecordMethodHandle();
/**
* 该Reflector对应封装的Class类型
*/
private final Class<?> type;
/**
* 可读 可写属性的名称集合
*/
private final String[] readablePropertyNames;
private final String[] writablePropertyNames;
/**
* 可读、可写属性对应的 getter 方法和 setter 方法集合,key 是属性的名称,value 是一个 Invoker 对象.Invoker 是对 Method 对象的封装
*/
private final Map<String, Invoker> setMethods = new HashMap<>();
private final Map<String, Invoker> getMethods = new HashMap<>();
/**
* 属性对应的 getter 方法返回值以及 setter 方法的参数值类型,key 是属性名称,value 是方法的返回值类型或参数类型
*/
private final Map<String, Class<?>> setTypes = new HashMap<>();
private final Map<String, Class<?>> getTypes = new HashMap<>();
/**
* 默认构造方法
*/
private Constructor<?> defaultConstructor;
/**
* 所有属性名称的集合,记录到这个集合中的属性名称都是大写的
*/
private final Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
// ...略
} -
构造一个 Reflector 对象的时候,传入一个 Class 对象,通过解析这个 Class 对象,即可填充上述核心字段,整个核心流程大致可描述为如下.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public Reflector(Class<?> clazz) {
// 用 type 字段记录传入的 Class 对象
type = clazz;
// 通过反射拿到 Class 类的全部构造方法,并进行遍历,过滤得到唯一的无参构造方法来初始化 defaultConstructor 字段.
// 这部分逻辑在 addDefaultConstructor() 方法中实现
addDefaultConstructor(clazz);
Method[] classMethods = getClassMethods(clazz);
if (isRecord(type)) {
addRecordGetMethods(classMethods);
} else {
// 1. 读取 Class 类中的 getter方法,填充上面介绍的 getMethods 集合和 getTypes 集合.
// 这部分逻辑在 addGetMethods() 方法中实现
addGetMethods(classMethods);
// 2. 读取 Class 类中的 setter 方法,填充上面介绍的 setMethods 集合和 setTypes 集合.
// 这部分逻辑在 addSetMethods() 方法中实现
addSetMethods(classMethods);
// 3. 读取 Class 中没有 getter/setter 方法的字段,生成对应的 Invoker 对象,填充 getMethods 集合、getTypes 集合以及 setMethods 集合、setTypes 集合.
// 这部分逻辑在 addFields() 方法中实现.
addFields(clazz);
}
// 根据前面三步构造的 getMethods/setMethods 集合的 keySet,初始化 readablePropertyNames、writablePropertyNames 集合
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
// 遍历构造的 readablePropertyNames、writablePropertyNames 集合,将其中的属性名称全部转化成大写并记录到 caseInsensitivePropertyMap 集合中
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
核心方法解析
-
addGetMethods()
方法和addSetMethods()
方法,它们分别用来解析传入 Class 类中的getter()
方法和setter()
方法,两者的逻辑十分相似.这里,以addGetMethods()
方法为例深入分析,其主要包括如下三个核心步骤.-
第一步,获取方法信息.会调用
getClassMethods()
方法获取当前 Class 类的所有方法的唯一签名(注意一下,这里同时包含继承自父类以及接口的方法),以及每个方法对应的 Method 对象.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50private Method[] getClassMethods(Class<?> clazz) {
Map<String, Method> uniqueMethods = new HashMap<>();
Class<?> currentClass = clazz;
while (currentClass != null && currentClass != Object.class) {
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
// we also need to look for interface methods -
// because the class may be abstract
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
currentClass = currentClass.getSuperclass();
}
Collection<Method> methods = uniqueMethods.values();
return methods.toArray(new Method[0]);
}
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
for (Method currentMethod : methods) {
if (!currentMethod.isBridge()) {
// signature format: returnType#methodName:parameterTypes
// for example: java.lang.String#addGetMethods:java.lang.Class
String signature = getSignature(currentMethod);
// check to see if the method is already known
// if it is known, then an extended class must have
// overridden a method
if (!uniqueMethods.containsKey(signature)) {
uniqueMethods.put(signature, currentMethod);
}
}
}
}
private String getSignature(Method method) {
StringBuilder sb = new StringBuilder();
Class<?> returnType = method.getReturnType();
if (returnType != null) {
sb.append(returnType.getName()).append('#');
}
sb.append(method.getName());
Class<?>[] parameters = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
sb.append(i == 0 ? ':' : ',').append(parameters[i].getName());
}
return sb.toString();
} -
在递归扫描父类以及父接口的过程中,会使用 Map<String, Method> 集合记录遍历到的方法,实现去重的效果,其中 Key 是对应的方法签名,Value 为方法对应的 Method 对象.生成的方法签名的格式如下:
1
2
3返回值类型#方法名称:参数类型列表
java.lang.String#addGetMethods:java.lang.Class- 生成的方法签名是包含返回值,可以作为该方法的唯一的标识.
-
第二步,按照Java的规范,从上一步返回的Method数组中查找getter方法,将其记录到
conflictingGetters
集合中.这里的conflictingGetters
集合(HashMap<String, List>()类型)中的 Key 为属性名称,Value 是该属性对应的 getter 方法集合.- 一个属性会查找到多个getter方法的原因是由于类间继承导致的,在子类中我们可以覆盖父类的方法,覆盖不仅可以修改方法的具体实现,还可以修改方法的返回值,getter方法也不例外,这就导致在第一步产生了两个签名不同的方法.
1
2
3
4
5
6
7
8
9private void addGetMethods(Method[] methods) {
// 按照 Java 的规范,从上一步返回的 Method 数组中查找 getter 方法,将其记录到 conflictingGetters 集合中
// conflictingGetters 集合(HashMap<String, List>()类型)中的 Key 为属性名称,Value 是该属性对应的 getter 方法集合
Map<String, List<Method>> conflictingGetters = new HashMap<>();
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
// 解决方法签名冲突
resolveGetterConflicts(conflictingGetters);
} -
第三步,解决方法签名冲突.这里会调用
resolveGetterConflicts()
方法对这种getter
方法的冲突进行处理,处理冲突的核心逻辑其实就是比较 getter 方法的返回值,优先选择返回值为子类的 getter 方法,例如:1
2
3
4
5// 该方法定义在SuperClazz类中
public List getA();
// 该方法定义在SubClazz类中,SubClazz继承了SuperClazz类
public ArrayList getA();-
在 resolveGetterConflicts() 方法处理完上述 getter 方法冲突之后,会为每个 getter 方法创建对应的 MethodInvoker 对象,然后统一保存到 getMethods 集合中.同时,还会在 getTypes 集合中维护属性名称与对应 getter 方法返回值类型的映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
// 处理冲突的核心逻辑其实就是比较 getter 方法的返回值,优先选择返回值为子类的 getter 方法
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
// 先初始化一个变量 winner 为 null,表示目前还没有确定的优胜的 getter 方法
Method winner = null;
String propName = entry.getKey();
boolean isAmbiguous = false;
for (Method candidate : entry.getValue()) {
// 如果 winner 为 null,则将当前 getter 方法赋值给 winner 变量,并继续下一个 getter 方法的比较
if (winner == null) {
winner = candidate;
continue;
}
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
// 如果 winner 不为 null,需要比较当前 getter 方法的返回值类型
if (candidateType.equals(winnerType)) {
// 如果当前 getter 方法的返回值类型与 winner 的返回值类型相同,且不是 boolean 类型,则说明存在歧义,设置 isAmbiguous 为 true,然后跳出循环
if (!boolean.class.equals(candidateType)) {
isAmbiguous = true;
break;
}
// 如果当前 getter 方法的返回值类型与 winner 的返回值类型相同,
// 但都是 boolean 类型,且当前 getter 方法以 "is" 开头,则将当前 getter 方法赋值给 winner,以保留以 "is" 开头的布尔类型 getter 方法
if (candidate.getName().startsWith("is")) {
winner = candidate;
}
// 如果当前 getter 方法的返回值类型是 winner 的返回值类型的子类或实现类,则说明当前 getter 方法更具体,继续下一个 getter 方法的比较
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
} else if (winnerType.isAssignableFrom(candidateType)) {
// 如果当前 getter 方法的返回值类型是 winner 的返回值类型所在的父类或接口,则说明当前 getter 方法不如 winner 具体,将 winner 赋值为当前 getter 方法
winner = candidate;
} else {
// 如果以上条件都不满足,说明存在歧义,设置 isAmbiguous 为 true,然后跳出循环
isAmbiguous = true;
break;
}
}
// 最后,将处理完成的属性名、优胜的 getter 方法和 isAmbiguous 标志传递给 addGetMethod 方法进行处理
addGetMethod(propName, winner, isAmbiguous);
}
} -
在通过
addGetMethods()
和addSetMethods()
方法,完成Class
类中getter/setter
方法的处理之后,会继续调用addFields()
方法处理没有getter/setter
方法的字段. -
以处理没有
getter
方法的字段为例,addFields()
方法会为这些字段生成对应的GetFieldInvoker
对象并记录到getMethods
集合中,同时也会将属性名称和属性类型记录到getTypes
集合中.处理没有setter
方法的字段也是相同的逻辑.
-
-
Invoker
-
在 Reflector 对象的初始化过程中,所有属性的
getter/setter
方法都会被封装成MethodInvoker
对象,没有getter/setter
的字段也会生成对应的Get/SetFieldInvoker
对象.下面我们就来看看这个Invoker
接口的定义:1
2
3
4
5
6public interface Invoker {
// 调用底层封装的Method方法或是读写指定的字段
Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
// 返回属性的类型
Class<?> getType();
}- 其中,MethodInvoker 是通过反射方式执行底层封装的 Method 方法(例如,getter/setter 方法)完成属性读写效果的,Get/SetFieldInvoker 是通过反射方式读写底层封装的 Field 字段,进而实现属性读写效果的.
ReflectorFactory
1 | public interface ReflectorFactory { |
- Reflector 初始化过程会有一系列的反射操作,为了提升 Reflector 的初始化速度,MyBatis 提供了 ReflectorFactory 这个工厂接口对 Reflector 对象进行缓存,其中最核心的方法是用来获取
Reflector
对象的findForClass()
方法 DefaultReflectorFactory
是ReflectorFactory
接口的默认实现,它默认会在内存中维护一个ConcurrentHashMap<Class<?>, Reflector>
集合(reflectorMap 字段)缓存其创建的所有 Reflector 对象.- 在其
findForClass()
方法实现中,首先会根据传入的Class
类查询reflectorMap
缓存,如果查找到对应的Reflector
对象,则直接返回;否则创建相应的Reflector
对象,并记录到reflectorMap
中缓存,等待下次使用.
默认对象工厂
ObjectFactory
是 MyBatis 中的反射工厂,其中提供了两个 create() 方法的重载,我们可以通过两个 create() 方法创建指定类型的对象.DefaultObjectFactory
是ObjectFactory
接口的默认实现,其 create() 方法底层是通过调用instantiateClass()
方法创建对象的.instantiateClass() 方法会通过反射的方式根据传入的参数列表,选择合适的构造函数实例化对象.- 除了使用
DefaultObjectFactory
这个默认实现之外,还可以在mybatis-config.xml
配置文件中配置自定义ObjectFactory
接口扩展实现类
属性解析工具
- 在 ResultMap 映射中,如果要配置 Order 与 OrderItem 的一对多关系,可以使用
<collection>
标签进行配置;如果 OrderItem 个数明确,可以直接使用数组下标索引方式(即 ordersItems[0])填充 orderItems 集合. - 这里的 “.” 导航以及数组下标的解析,也都是在反射工具箱中完成的. reflection.property 包下的三个属性解析相关的工具类,在后面的 MetaClass、MetaObject 等工具类中,也都需要属性解析能力.
PropertyTokenizer
工具类负责解析由“.”和“[]”构成的表达式.PropertyTokenizer
继承了 Iterator 接口,可以迭代处理嵌套多层表达式.PropertyCopier
是一个属性拷贝的工具类,提供了与 Spring 中 BeanUtils.copyProperties() 类似的功能,实现相同类型的两个对象之间的属性值拷贝,其核心方法是 copyBeanProperties() 方法.PropertyNamer
工具类提供的功能是转换方法名到属性名,以及检测一个方法名是否为 getter 或 setter 方法.
MetaClass
- MetaClass 提供了获取类中属性描述信息的功能,底层依赖前面介绍的 Reflector,在 MetaClass 的构造方法中会将传入的 Class 封装成一个 Reflector 对象,并记录到 reflector 字段中,MetaClass 的后续属性查找都会使用到该 Reflector 对象.
- MetaClass 中的 findProperty() 方法是实现属性查找的核心方法,它主要处理了“.”导航的属性查找,该方法会用前文介绍的 PropertyTokenizer 解析传入的 name 表达式,该表达式可能通过“.”导航多层,例如,order.deliveryAddress.customer.name.
- MetaClass 会逐层处理这个表达式,首先通过 Order 类型对应的 Reflector 查找 deliveryAddress 属性,查找成功之后,根据 deliveryAddress 属性的类型(即 Address 类型)创建对应的 MetaClass 对象(以及底层的 Reflector 对象),再继续查找其中的 customer 属性,如此递归处理,直至最后查找到 Customer 中的 name 属性.这部分递归查找逻辑位于 MetaClass.buildProperty() 方法中.
- 在上述 MetaClass 查找属性的过程中,还会调用 hasGetter() 和 hasSetter() 方法负责判断属性表达式中指定的属性是否有对应的 getter/setter 方法.这两个方法也是先通过 PropertyTokenizer 解析传入的 name 表达式,然后进行递归查询,在递归查询中会依赖 Reflector.hasGetter() 方法查找前文介绍的 getMethods 集合或 setMethods 集合,查找属性对应的 getter/setter 方法.
- MetaClass 中的其他方法实现也都大多是依赖 PropertyTokenizer 解析表达式,然后递归查找,查找过程会依赖 Reflector 的相关方法.
ObjectWrapper
-
MetaClass 中封装的是 Class 元信息,ObjectWrapper 封装的则是对象元信息.在 ObjectWrapper 中抽象了一个对象的属性信息,并提供了查询对象属性信息的相关方法,以及更新属性值的相关方法.
-
BaseWrapper 是 ObjectWrapper 接口的抽象实现,其中只有一个 MetaObject 类型的字段.BaseWrapper 为子类实现了 resolveCollection()、getCollectionValue() 和 setCollectionValue() 三个针对集合对象的处理方法.其中,resolveCollection() 方法会将指定属性作为集合对象返回,底层依赖 MetaObject.getValue()方法实现(后面还会详细介绍).getCollectionValue() 方法和 setCollectionValue() 方法会解析属性表达式的下标信息,然后获取/设置集合中的对应元素,这里解析属性表达式依然是依赖前面介绍的 PropertyTokenizer 工具类.
-
BeanWrapper 继承了 BaseWrapper 抽象类,底层除了封装了一个 JavaBean 对象之外,还封装了该 JavaBean 类型对应的 MetaClass 对象,以及从 BaseWrapper 继承下来的 MetaObject 对象.
-
在 get() 方法和 set() 方法实现中,BeanWrapper 会根据传入的属性表达式,获取/设置相应的属性值.以 get() 方法为例,首先会判断表达式中是否含有数组下标,如果含有下标,会通过 resolveCollection() 和 getCollectionValue() 方法从集合中获取相应元素;如果不包含下标,则通过 MetaClass 查找属性名称在 Reflector.getMethods 集合中相应的 GetFieldInvoker,然后调用 Invoker.invoke() 方法读取属性值.
-
BeanWrapper 中其他方法的实现也大都与 get() 方法和 set() 方法类似,依赖 MetaClass、MetaObject 完成相关对象中属性信息读写.
-
CollectionWrapper 是 ObjectWrapper 接口针对 Collection 集合的一个实现,其中封装了Collection
<Object>
集合对象,只有 isCollection()、add()、addAll() 方法以及从 BaseWrapper 继承下来的方法是可用的,其他方法都会抛出 UnsupportedOperationException 异常.MapWrapper 是针对 Map 类型的一个实现.
MetaObject
- ObjectWrapper 实现了读写对象属性值、检测getter/setter 等基础功能,在分析 BeanWrapper 等实现类时,可以看到其底层会依赖 MetaObject.在 MetaObject 中维护了一个 originalObject 字段指向被封装的 JavaBean 对象,还维护了该 JavaBean 对象对应的 ObjectWrapper 对象(objectWrapper 字段).
- MetaObject 和 ObjectWrapper 中关于类级别的方法,例如,hasGetter() 方法、hasSetter() 方法、findProperty() 方法等,都是直接调用 MetaClass 或 ObjectWrapper 的对应方法实现的.其他关于对象级别的方法,都是与 ObjectWrapper 配合实现,例如 MetaObject.getValue()/setValue() 方法等.
- 这里以 getValue() 方法为例,该方法首先根据 PropertyTokenizer 解析指定的属性表达式,如果该表达式是包含“.”导航的多级属性查询,则获取子表达式并为其对应的属性对象创建关联的 MetaObject 对象,继续递归调用 getValue() 方法,直至递归处理结束,递归出口会调用 ObjectWrapper.get() 方法获取最终的属性值.
- 在 MetaObject 中,setValue() 方法的核心逻辑与 getValue() 方法基本类似,也是递归查找.但是,其中有一个不同之处需要你注意:如果需要设置的最终属性值不为空时,在递归查找 setter() 方法的过程中会调用 ObjectWrapper.instantiatePropertyValue() 方法初始化递归过程中碰到的任意空对象,但如果碰到为空的集合元素,则无法通过该方法初始化.ObjectWrapper.instantiatePropertyValue() 方法实际上是依赖 ObjectFactory 接口的 create()方法(默认实现是 DefaultObjectFactory )创建相应类型的对象.