历史文章 :
Mybatis 源码学习(1)-解析器模块
由于 JDK 提供的反射机制过于复杂,因此 Mybatis 对常用的反射机制做了封装,以简化反射 API,这部分封装代码在 org.apache.ibatis.reflection
包中。
Reflector 主要用于解析一个 Class 对象,将其类型、构造函数、set方法、get 方法做解析,方便随时使用。这里的 get/set 方法解析,只根据方法进行解析,而不关心其中是否存在对应的 filed,如:getA()、setA(),即使没有对应的字段 a,也会认为对象存在属性 a。
字段说明
Reflector 解析后的字段包括:
// 对应的 Class 类型
private final Class<?> type;
// 可读属性的名称集合,可读属性就是存在相应 getter 方法的属性
private final String[] readablePropertyNames;
// 可写属性的名称集合,可写属性就是存在相应 setter 方法的属性
private final String[] writeablePropertyNames;
// 记录了属性相应的 setter 方法,key 是属性名称,value 是 Invoker 对象,
// 它是对 setter 方法对应 Method 对象的封装
private final Map<String, Invoker> setMethods = new HashMap<>();
// 属性相应 的 getter 方法集合,key 是属性名称,value 也是 Invoker 对象
private final Map<String, Invoker> getMethods = new HashMap<>();
// 记录了属性相应的 setter 方法的参数值类型(第一个参数的类型),
// key 是属性名称,value 是 setter 方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
// 记录了属性相应的 getter 方法的返回位类型,
// key是属性名称,value 是 getter 方法的返回位类型
private final Map<String, Class<?>> getTypes = new HashMap<>();
// 记录了默认构造方法
private Constructor<?> defaultConstructor;
// 记录了所有属性名称的集合
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
Reflector 的核心处理过程都在它的构造器中实现,通过 java 的反射机制,构造对应字段的信息,解析过程如下。
public Reflector(Class<?> clazz) {
type = clazz; // 初始化 type 字段,即需要处理的 class 对象
// 查找默认构造方法,具体实现是通过反射遍历所有构造方法,
// 获取参数个数为0的构造方法
addDefaultConstructor(clazz);
// 处理 clazz 中的 getter 方法,填充 getMethods 集合和 getTypes 集合
addGetMethods(clazz);
// 处理 clazz 中的 setter 方法,填充 setMethods 集合和 setTypes 集合
addSetMethods(clazz);
// 处理没有 getterI setter 方法的字段
addFields(clazz);
// 根据 getMethods/setMethods 集合,初始化可读 /写属性的名称集合
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
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);
}
}
addDefaultConstructor 方法比较简单,就是简单的通过 clazz.getDeclaredConstructors() ,然后遍历所有构造器,选取参数个数为0的构造器为默认构造器。
addGetMethods 的处理过程分为三步:
conflictingGetters(Map>)
对象;比如,对于父类 A 和子类 SubA,都有 getNames 方法,但是父类的返回值是 List
,子类的返回值 ArrayList
,此时解析到的方法名称是 java.util.List#getNames
和 java.util.ArrayList#getNames
,而我们需要的是子类的方法而不是父类的方法,即我们需要的是 java.util.ArrayList#getNames
。
1 解析签名和 Method
// 解析 Method 方法
private Method[] getClassMethods(Class<?> cls) {
// 用于记录指定类中定义的全部方法的唯一签名以及对应的 Method 对象
Map<String, Method> uniqueMethods = new HashMap<String, Method>();
Class<?> currentClass = cls;
while (currentClass != null && currentClass != Object.class) {
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
// 查看接口中的定义,避免抽象方法
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
// 继续向父类进行遍历
currentClass = currentClass.getSuperclass();
}
Collection<Method> methods = uniqueMethods.values();
// 转换成 Methods 数纽返回
return methods.toArray(new Method[methods.size()]);
}
// 生成方法的签名
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
for (Method currentMethod : methods) {
if (!currentMethod.isBridge()) {
// 通过 Reflector.getSignature () 方法得到的方法签名是:
// 返回值类型#方法名称:参数类型列表。
// 例如,Reflector.getSignature(Method)方法的唯一签名是:
// java.lang.String#getSignature:java.lang.reflect.Method
// 通过 Reflector.getSignature() 方法得到的方法签名是全局唯一的,
// 可以作为该方法的唯一标识
String signature = getSignature(currentMethod);
// 检测是否在子类中已经添加过该方法,如果在子类中已经添加过,
// 无须再向 uniqueMethods 集合中添加该方法了
if (!uniqueMethods.containsKey(signature)) {
if (canAccessPrivateMethods()) {
try {
currentMethod.setAccessible(true);
} catch (Exception e) {
}
}
// 记录该签名和方法的对应关系
uniqueMethods.put(signature, currentMethod);
}
}
}
}
2 将 getter 方法名与对应 Method 关联起来
private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
List<Method> list = conflictingMethods.get(name);
if (list == null) {
list = new ArrayList<Method>();
conflictingMethods.put(name, list);
}
list.add(method);
}
3 解决父子类方法冲突
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
// 遍历 conflictingGetters 集合
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
// 遍历 key 对应的 List
for (Method candidate : entry.getValue()) {
// 第一次找到该 key,直接返回
if (winner == null) {
winner = candidate;
continue;
}
// 多次找到该 key 对应的 Method
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
// 没有使用多态,直接子类重写父类方法
if (candidateType.equals(winnerType)) {
// 当某字段存在多个 getter 方法,则可能是 get/is 方法并存
// 并存时,则必需是 boolean 值(否则不符合 JavaBean 规范),
// 即非 boolean 值不允许用 isXXX 方法,
// 因此,此处检查返回值类型,必需是 boolean 值
if (!boolean.class.equals(candidateType)) {
throw new ReflectionException(…);
} else if (candidate.getName().startsWith("is")) {
// 如果存在 isXXX 方法,以 is 方法为准
// 否则即可认为使用第一个找到的方法
winner = candidate;
}
} else if (candidateType.isAssignableFrom(winnerType)) {
// winnerType 是更具体的类,是 candidateType 的子类
} else if (winnerType.isAssignableFrom(candidateType)) {
// candidateType 更具体,需返回子类
winner = candidate;
} else {
// 其他均是异常
throw new ReflectionException(…);
}
}
addGetMethod(propName, winner);
}
}
// 继续完成对 getMethods 和 getTypes 的填充
private void addGetMethod(String name, Method method) {
// 检测属性名是否合法(非$开头,非 class,非序列化 id)
if (isValidPropertyName(name)) {
// 将属性名以及对应的 MethodInvoker 对象添加到 getMethods 集合
getMethods.put(name, new MethodInvoker(method));
// 获取返回值的 Type
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
// 将属性名称及其 getter 方法的返回值类型添加到 getTypes 集合中
getTypes.put(name, typeToClass(returnType));
}
}
addGetMethods 的整体处理过程如下:
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
// 步骤 1,解析类及类的父接口的方法
Method[] methods = getClassMethods(cls);
// 步骤 2,查找 getter 方法,并与对应 Method 关联到 conflictingGetters 中
for (Method method : methods) {
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
// JavaBean 规范的取值方法只有 getter/is,且比前缀长
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
// 解析对应属性名称
name = PropertyNamer.methodToProperty(name);
// 将属性名与对应的 Method 记录到 conflictingGetters 中
addMethodConflict(conflictingGetters, name, method);
}
}
// 步骤 3,对 conflictingGetters 集合解决冲突
resolveGetterConflicts(conflictingGetters);
}
addSetMethods 的逻辑和 addGetMethods 的过程类似,但是它们的处理冲突的逻辑不一样。
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List<Method> setters = conflictingSetters.get(propName);
// 检查是否有 getter 方法
Class<?> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
// 遍历所有的 setter
for (Method setter : setters) {
// 只关心第一个参数
Class<?> paramType = setter.getParameterTypes()[0];
// 直接返回与 getter 方法参数类型相同的方法
if (paramType.equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (exception == null) {
try {
// 无异常时,返回最佳匹配
// 最佳匹配的逻辑是选取 match 和 setter 中更具体的那个类
// 如 List 和 ArrayList,返回 ArrayList
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
match = null;
exception = e;
}
}
}
if (match == null) {
throw exception;
} else {
addSetMethod(propName, match);
}
}
}
addFields 会把所有的无 getter/setter 的字段添加到 setMethods、setTypes、getMethods、getTypes中。
private void addFields(Class<?> clazz) {
// 获取 clazz 中定义的全部字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (canAccessPrivateMethods()) {
try {
field.setAccessible(true);
} catch (Exception e) {
}
}
if (field.isAccessible()) {
// 当 setMethods 集合不包含同名属性时,
// 将其记录到 setMethods 集合和 setTypes 集合
if (!setMethods.containsKey(field.getName())) {
int modifiers = field.getModifiers();
// 过滤掉 static && final 修饰的字段,因为 final 可以经过反射设值
if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
// addSetField 方法的功能是填充 setMethods 集合和 setTypes 集合
addSetField(field);
}
}
// 当 getMethods 集合中不包含同名属性时,
// 将其记录到 getMethods 集合和 getTypes 集合
if (!getMethods.containsKey(field.getName())) {
// addGetField 方法的功能是填充 getMethods 集合和 getTypes 集合
addGetField(field);
}
}
}
if (clazz.getSuperclass() != null) {
// 继续处理父类中定义的字段
addFields(clazz.getSuperclass());
}
}
Reflector中提供了多个 get*()方法用于读取上述集合生成的信息。
在 addSetField、addGetField、addGetMethod、addSetMethods中,会将 setter/getter 对应的 Method 对象或者字段对应的 Field 包装成 Invoker 对象。
public interface Invoker {
// 调用获取指定字段的值或执行指定的方法
Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
// 返回属性相应的类型
Class<?> getType();
}
其继承结构如下:
GetFieldInvoker/SetFieldInvoker 是对 Field 对象的封装,分别通过调用 Field.get()/set() 作为其对应的实现。MethodInvoker 是对 Method 对象的封装,通过调用 Method.invoke() 方法作为其实现。
ReflectorFactory 接口提供了对 Reflector 对象的创建和缓存功能,其定义如下:
public interface ReflectorFactory {
// 检测该 ReflectorFactory 对象是否会缓存 Reflector 对象
boolean isClassCacheEnabled();
// 设置缓存 Reflector 对象的标记
void setClassCacheEnabled(boolean classCacheEnabled);
// 创建指定 Class 对应的 Reflector 对象
Reflector findForClass(Class<?> type);
}
Mybatis 提供了 ReflectorFactory 的默认实现 DefaultReflectorFactory,它内部包含了一个 Reflector 相关的 ConcurrentMap 作为缓存,并且无过期时间设定。DefaultReflectorFactory 还提供了 findForClass 方法的实现,为尚未加入缓存的 Class 创建 Reflector 对象。
public class DefaultReflectorFactory implements ReflectorFactory {
// 该字段决定是否开启对 Reflector 对象的缓存
private boolean classCacheEnabled = true;
// 使用 ConcurrentMap 集合实现对 Reflector 对象的缓存
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>();
// 外部获取缓存标记
public boolean isClassCacheEnabled() {
return classCacheEnabled;
}
// 外部设置缓存标记
public void setClassCacheEnabled(boolean classCacheEnabled) {
this.classCacheEnabled = classCacheEnabled;
}
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) { // 检测是否开启缓存
Reflector cached = reflectorMap.get(type);
if (cached == null) {
cached = new Reflector(type); // 创建 Reflector 对象
reflectorMap.put(type, cached); // 放入 ConcurrentMap 中缓存
}
return cached;
} else {
return new Reflector(type); // 未开启缓存,则直接创建并返回Reflector对象
}
}
}
Reflector 工具集主要用于 JavaBean 解析,分别处理类型上的方法和字段,并且通过 ReflectorFactory 对外提供 Reflector 的缓存,方便 JavaBean 对象和数据库字段进行映射。
参考文档:《Mybatis 技术内幕》
本文的基本脉络参考自《Mybatis 技术内幕》,编写文章的原因是希望能够系统地学习 Mybatis 的源码,但是如果仅阅读源码或者仅从官方文档很难去系统地学习,因此希望参考现成的文档,按照文章的脉络逐步学习。
欢迎关注我的公众号:我的搬砖日记,我会定时分享自己的学习历程。