基础支持层——反射模块

MyBatis基础支持层位于 Mybatis 整体架构的最底层,支撑着 Mybatis 的核心处理层,是整个框架的基石。基础支持层中封装了多个较为通用的、独立的模块,不仅仅为 Mybatis 提供基础支撑,也可以在合适的场景中直接复用。

整体架构

这篇介绍MyBatis的反射模块

反射工具箱

Mybatis 在进行参数处理、结果映射等操作时,会涉及大量的反射操作。Java 中的反射虽然功能强大,但是代码编写起来比较复杂且容易出错,为了简化反射操作的相关代码,Mybatis 提供了专门的反射模块,该模块位于 org.apache.ibatis.reflection 包中,它对常见的反射操作做了进一步封装,提供了更加简洁方便的反射 API。

Reflector & ReflectorFactory

Reflector是MyBatis中反射模块的基础,每隔Reflector对象都对应一个类,在Reflector中缓存了反射操作需要使用的累的元信息。

Reflector中各个字段的含义如下:

Private Class <?> type; //对应的 C1 ass 类型

//可读属性的名称集合,可读属性就是存在相应 getter 方法的属性,初始值为空数组
private String I readablepropertynames= EMPTY STRING ARRAY

//可写属性的名称集合,可写属性就是存在相应 setter 方法的属性,初始值为空数组
private String [writeablepropertynames EMPTY STRING ARRAY;
//记录了属性相应的 setter 方法,key 是属性名称,value 是 Invoker 对象,它是对 setter 方法对应
// Me thod 对象的封装,后面会详细介绍
private Map <String, Invoker> setmethods =new Hashmap <String, Invoker> ();

//属性相应的 getter 方法集合,key 是属性名称,value 也是 Invoker 对象
private Map <String, Invoker> getmethods =new Hashmap <String, Invoker> ();

//记录了属性相应的 setter 方法的参数值类型,key 是属性名称,value 是 setter 方法的参数类型
private Map <String, Class <?>> settypes =new Hashmap <string, Class <?>> ();

//记录了属性相应的 getter 方法的返回值类型,key 是属性名称,value 是 getter 方法的返回值类型
private Map <string, Class <?>> gettypes=new Hashmap <string, Class <?>> ();

private Constructor <?> default Constructor; //记录了默认构造方法

//记录了所有属性名称的集合
private Map <string, String> caseinsensitivepropertymap-new Hashmap <String, String> ();

在Reflector的构造方法中会解析制定的Class对象,并填充上述集合。具体如下:

public Reflector(Class<?> clazz) {
type = clazz; // 初始化type字段
// 查找clazz的默认构造方法,具体实现是通过反射遍历所有构造方法
addDefaultConstructor(clazz);
addGetMethods(clazz); // 处理clazz中的getter方法,填充getMethods集合和getTypes集合
addSetMethods(clazz); // 处理clazz中的setter方法,填充setMethods集合和setTypes集合
addFields(clazz); // 处理没有getter/setter方法的字段

// 根据getMethods/setMethods集合,初始化可读/写属性的名称集合
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);

// 初始化caseInsensitivePropertyMap集合,其中记录了所有大写格式的属性名称
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(
propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(
propName.toUpperCase(Locale.ENGLISH), propName);
}
}

Reflector.Addgetmethodso()方法主要负责解析类中定义的 getter 方法,Reflector. addSetMethods()方法负责解析类中定义的 setter 方法,两者的逻辑类似。

Reflector. Addgetmethods() 方法有如下三个核心步骤。

  1. 首先,调用Reflector.getClassMethods()方法获取当前类及其父类中定义的所有方法的唯一签名以及响应的Method对象。

    private Method[] getClassMethods(Class<?> clazz) {
    // 用于记录制定类中定义的全部方法的唯一签名以及对应的Method对象
    Map<String, Method> uniqueMethods = new HashMap<>();
    Class<?> currentClass = clazz;
    while (currentClass != null && currentClass != Object.class) {

    // 记录currentClass这个类中定义的全部方法
    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());
    }
    // 获取父类,继续while循环
    currentClass = currentClass.getSuperclass();
    }

    Collection<Method> methods = uniqueMethods.values();

    return methods.toArray(new Method[0]);// 转换成Methods数组返回
    }
  2. 然后,按照JavaBean的规范,从Reflector.getClassMethods()方法返回的Method数组中查找该类中定义的getter方法,将其记录到conflictingGetters集合中。conflictingGetters集合的key为属性名称,value是该属性对应的getter方法的集合。

  3. 当子类覆盖了父类的getter方法且返回值发生变化时,在步骤1中就会产生两个签名不同的方法。

TypeParameterResolver

在开 始介绍 TypeParameterResolver之前,先简 单 介绍 一下Type接口的基础 知识 。Type是所有类 型的父接口,它 有四个 子接口和一个 实 现 类 ,如图所示。

image-20200606160310736

下面来 看这 些子接口和子类 所代表的类 型。

  • Class比较 常见 ,它 表示的是原始类 型。

    Class类 的对 象表示JVM中的一个 类 或接口, 每个 Java类 在JVM里都表现 为 一个 Class对 象。

    在程序中可以通过 “类名.class”、“**对象.getClass()”或是“Class.forName(“类 名”)**”等方式获 取Class对 象。

    数 组 也被映射为 Class对 象,所有元素类 型相同且维 数 相同的数 组 都共享同一个 Class对 象。

  • ParameterizedType 表示的是参 数 化类 型,例如 List、 Map<Integer,String>、 Service这 种 带 有泛型的类 型。

    ParameterizedType接口中常用的方法有三个 ,分别 是:

    • Type getRawType()—–返回参 数 化类 型中的原始类 型,例如List的原始类 型为 List。
    • Type[] getActualTypeArguments()—–获 取参 数 化类 型的类 型变 量或是实 际 类 型列 表,例如Map<Integer,String>的实 际 泛型列表Integer和 String。需要注意的是, 该 列表的元素类 型都是Type,也就是说 ,可能存在多层 嵌套的情况 。
    • Type getOwnerType()—–返回是类 型所属 的类 型,例如存在A类 ,其中定义 了 内 部类 InnerA,则 InnerA属 的类 型为 A,如果是顶 层 类 型则 返回null。 这 种 关 系比较 常见 的示例是Map<K,V>接口与 Map.Entry<K,V>接口,Map<K,V> 接 口 是Map<K,V>接口的所有者。
  • TypeVariable表示的是类 型变 量,它 用来 反映在JVM编 译 该 泛型前的信息。

    例如List 中的T就是类 型变 量,它 在编 译 时 需被转 换 为 一个 具体 的类 型后才能正常使用。

    该 接口中常用的方法有三个 ,分别 是:

    • Type[] getBounds()—–获 取类 型变 量的上边 界,如果未明确 声 明上边 界则 默认 为 Object。

      例如 class Test中 K 的上界就是 Person。

    • D getGenericDeclaration()—–获 取声 明该 类 型变 量的原始类 型,例 如 class Test 中 的原始类 型是 Test。

    • String getName()— 获 取在源码 中定义 时 的名字,上例中为 K。

  • GenericArrayType 表示的是数 组 类 型且组 成元素是 ParameterizedType 或 TypeVariable。

    例如 List或T[]。该 接口只有 Type getGenericComponentType()—个 方法,它 返回数 组 的组 成元素。

  • WildcardType 表示的是通配符泛型,例如? extends Number? uper Integer

    WildcardType接口有两 个 方法,分别 是:

    • Type[] getUpperBounds()—-返回泛型变 量的上界。
    • Type[] getLowerBounds()—- 返回泛型变 量的下界。

回到对 TypeParameterResolve,它 是一个 工具类 ,提供了一系列静 态 方法来 解析指定类 中的字段、方法返回值 或方法参 数 的类 型。TypeParameterResolver中各个 静 态 方法之间 的调 用关 系大致如下图 所示,为 保持清 晰 ,其中递 归 调 用没 有表现 出来 ,在后面 的代码 分析过 程中会 进 行强调 。

image-20200606162023961

TypeParameterResolver 中 通 过 resolveFieldType()方 法 、 resolveRetumType()方 法 、 resolveParamTypes()方法分别 解析字段类 型、方法返回值 类 型和方法参 数 列表中各个 参 数 的类 型。 这 三个 方法的逻 辑 基本类 似,这 里以resolveFieldType()方法为 例进 行介绍 ,TypeParameterResolver.resolveFieldType()方法的具体 实 现 如下:

/**
* @return The field type as {@link Type}. If it has type parameters in the declaration,
* they will be resolved to the actual runtime {@link Type}s.
*/
public static Type resolveFieldType(Field field, Type srcType) {
Type fieldType = field.getGenericType();
Class<?> declaringClass = field.getDeclaringClass();
return resolveType(fieldType, srcType, declaringClass);
}

上述三个 方法都会 调 用resolveType()方法,该 方法会 根据其第一个 参 数 的 类 型,即 字段、方法返回值 或方法参 数 的类 型,选 择 合适的方法进 行解析。resolveType()方法的 第二个 参 数 表示查 找 该 字段、返回值 或方法参 数 的起始位置。第三个 参 数 则 表示该 字段、方法 定义 所在的类 。TypeParameterResolver.resolveType()方法代码 如下:

private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
if (type instanceof TypeVariable) {
// 解析TypeVariable类型
return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
} else if (type instanceof ParameterizedType) {
// 解析ParameterizedType类型
return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
} else if (type instanceof GenericArrayType) {
// 解析genericArrayType类型
return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
} else {
return type; // class类型
}
// 字段、返回值 、参 数 不可能直接定义 成WildcardType类 型,但可以嵌套在别 的类 型中
}

为了便于理解,通过一个示例分析resolveType方法, 假设有三个类 ClassA、SubClassA、TestType,代码如下:

ClassA

public class ClassA <K, V> { 
protected Map<K, V> map;
// ••• map 的 getter/setter 方 法 (略 )
}

SubClassA

public class SubClassA <T> extends ClassA<T,T> { 
// ......
}

TestType

public class TestType {

SubClassA<Long> sa = new SubClassA();

public static void main(String[] args) throws NoSuchFieldException {
Field f = ClassA.class.getDeclaredField("map");
System.out.println(f.getGenericType());
System.out.println(f.getGenericType() instanceof ParameterizedType);
// 输出:
// java.util.Map<K, V>
// true

// 解析SubA<Long>(ParameterizedType类 型)中的map字段,注意:ParameterizedTypelmpl是
// 在 sun.reflect.generics.reflectiveObjects 包下的 ParameterizedType接口实 现

Type type = TypeParameterResolver.resolveFieldType(
f, ParameterizedTypelmpl.make(SubClassA.class,
new Type[]{Long.class}, TestType.class));

//也可以使用下面的方式生成上述ParameterizedType对 象,
// 并 调 用 TypeParameterResolver.resolveFieldType ()方 法 :
//
// TypeParameterResolver.resolveFieldType(f,
// TestType.class.getDeclaredField("sa").getGenericType());
System.out.println(type.getClass());
// 输 出 :class TypeParameterResolver$ParameterizedTypelmpl

// 注意,TypeParameterResolver$ParameterizedTypeImpl是ParameterizedType接口的实 现
ParameterizedType p = (ParameterizedType) type;
System.out.println(p.getRawType());
// 输 出 :interface java.util.Map

System.out.println(p.getOwnerType());
// 输 出:null

for (Type t : p .getActualTypeArguments()) {
System.out.println(t);
}
//输 出:
// class java.lang.Long
// class java.lang.Long
}
}

根据前面的Type接口的介绍,上例中ClassA.map字段声明的类型Map<K,V>是ParamelerizedType类型,resolveType()方法回调用resolvParameterizedType()方法进行解析。

首先介绍resolveParameterizedType()方法的参数:

  • 第一个参数是待解析的ParameterizedType类型
  • 第二个参数是解析操作的起始类型
  • 第三个参数为定义该字段或方法的类的Class对象

在该示例中第一个参数是Map<K,V>对应的ParameterizedType对象,第二个参数是TypeTest.SubA对应的ParameterizedType对象,第三个参数是ClassA(声明map字段的类)相应的Class对象。

继续分析scanSuperTypes()方法,该方法回递归整个继承结构并完成类型变量的解析。在该示例之中,第一个参数K对应的TypeVariable对象,第二个参数是TypeText.SubA对应的ParameterizedType对象,第三个参数是ClassA(声明map字段的类)对应的Class对象,第四个参数是SubClassA对应的Class对象,第五个参数是Class<T,T>对应的ParameterizedType对象。

scanSuperTypes()方法的具体实现如下:

private static Type scanSuperTypes(
TypeVariable<?> typeVar, Type srcType,
Class<?> declaringClass, Class<?> clazz, Type superclass) {
Type result = null;
if (superclass instanceof ParameterizedType) {
ParameterizedType parentAsType = (ParameterizedType) superclass;
Class<?> parentAsClass = (Class<?>) parentAsType.getRawType();
if (declaringClass == parentAsClass) {
Type[] typeArgs = parentAsType.getActualTypeArguments();
TypeVariable<?>[] declaredTypeVars =
declaringClass.getTypeParameters();
for (int i = 0; i < declaredTypeVars.length; i++) {
if (declaredTypeVars[i] == typeVar) {
if (typeArgs[i] instanceof TypeVariable) {
TypeVariable<?>[] typeParams = clazz.getTypeParameters();
for (int j = 0; j < typeParams.length; j++) {
if (typeParams[j] == typeArgs[i]) {
if (srcType instanceof ParameterizedType) {
result = ((ParameterizedType) srcType).getActualTypeArguments()[j];
}
break;
}
}
} else {
result = typeArgs[i];
}
}
}
} else if (declaringClass.isAssignableFrom(parentAsClass)) {
result = resolveTypeVar(typeVar, parentAsType, declaringClass);
}
} else if (superclass instanceof Class) {
if (declaringClass.isAssignableFrom((Class<?>) superclass)) {
result = resolveTypeVar(typeVar, superclass, declaringClass);
}
}
return result;
}

下图展示了scanSuperTypes()方法解析类型变量的核心逻辑。

image-20200607094904927

ObjectFactory

MyBatis中很多模块会使用到ObjectFactory接口,该接口提供了多个create()方法的重载,通过这些create()方法可以创建指定类型的对象。ObjectFactory的定义如下:

public interface ObjectFactory {

/**
* Sets configuration properties.
* 设置配置信息
* @param properties configuration properties
*/
void setProperties(Properties properties);

/**
* Creates a new object with default constructor.
* 通过无参构造器创建指定类的对象
* @param type Object type
* @return
*/
<T> T create(Class<T> type);

/**
* Creates a new object with the specified constructor and params.
* 根据参数列表,从指定类型中选择合适的构造器创建对象
* @param type Object type
* @param constructorArgTypes Constructor argument types
* @param constructorArgs Constructor argument values
* @return
*/
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

/**
* Returns true if this object can have a set of other objects.
* It's main purpose is to support non-java.util.Collection objects like Scala collections.
* 检测指定类型是否为集合类型,主要处理java.util.Collectiopn及其子类
*
* @param type Object type
* @return whether it is a collection or not
* @since 3.1.0
*/
<T> boolean isCollection(Class<T> type);

}

DefaultObjectFactoryMyBatis提供的ObjectFactory接口的唯一实 现 ,它 是一个 反射工厂 , 其 create()方法通过 调 用 instantiateClass()方法实 现 。

DefaultObjectFactory.instantiateClass()方法会 根据传 入的参 数 列表选 择 合适的构 造函数 实 例化对 象,具体 实 现 如下:

private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {
constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
}
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (Exception e) {
StringBuilder argTypes = new StringBuilder();
if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
for (Class<?> argType : constructorArgTypes) {
argTypes.append(argType.getSimpleName());
argTypes.append(",");
}
argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
}
StringBuilder argValues = new StringBuilder();
if (constructorArgs != null && !constructorArgs.isEmpty()) {
for (Object argValue : constructorArgs) {
argValues.append(String.valueOf(argValue));
argValues.append(",");
}
argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
}
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}

除了使用MyBatis提供的DefaultObjectFactory实现,还可以在mybatis-config.xml配置文件中指定自定义的ObjectFactory接口实现累, 从而实现功能上的扩展。

Property工具类

org.apache.ibatis.reflection.property 包下,提供了 PropertyCopier、PropertyNamer、PropertyTokenizer 三个属性相关的工具类。

PropertyTokenizer

在使用MyBatis的过程中,经常会碰到一些属性表达式,例如,在查询用户(User)的订单(Order)的结果集如下表所示:

user_name order item1 item2
Mary 124640 iPhone 8 plus MacBook Pro
Lisa 46546 iPhone 11 Pro Mac Pro

对象模型如下:

image-20200607100948201

假设现在需要将结果集中的item1列雨用户第一个订单(Order)的第一条目(Item)的名称映射,item2与用户第一个订单的(Order)的第二条目(Item)的名称映射(这里仅仅是一个示例,在实际生产中很少这样的设计),我们可以得到下面的映射规则:

<resultMap id="rm4testProTool" type="User">
<id column="id" property="id" />
<result property="orders[0].items[0].name" column="iteml" />
<result property="orders[0].items[1].name" column="item2" />
</resultMap>

在上面的例子中,orders[0].items[0].name这种由 .[]组成的表达式是由PropertyTokenizer进行解析的。以下是此类的源码:

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
// 当前表达式的名称
private String name;

// 当前表达式的索引名
private final String indexedName;

// 索引下标
private String index;

// 子表达式
private final String children;

public PropertyTokenizer(String fullname) {
// 查找 . 的位置
int delim = fullname.indexOf('.');
if (delim > -1) {
// 初始化 name
name = fullname.substring(0, delim);
// 初始化children
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
// 初始化indexname
indexedName = name;
delim = name.indexOf('[');
if (delim > -1) {
// 初始化index
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}

public String getName() {
return name;
}

public String getIndex() {
return index;
}

public String getIndexedName() {
return indexedName;
}

public String getChildren() {
return children;
}

@Override
public boolean hasNext() {
return children != null;
}

// next方法中会创建PropertyTokenizer对象并解析children字段记录的子表达式
@Override
public PropertyTokenizer next() {
return new PropertyTokenizer(children);
}

@Override
public void remove() {
throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
}
}

PropertyTokenizer继承了Iterator接口,它可以迭代处理嵌套多层表达式。

next()方法中会创建PropertyTokenizer对象并解析children字段记录的子表达式。

继续使用订单示例进行说明,描述解析属性表达式orders[0].items[0].name的迭代过程:

image-20200607102830771

PropertyNamer

PropertyNamer是另一个工具类,提供了下列静态方法帮助完成方法名到属性名的转换,以及多种检测操作。

public final class PropertyNamer {

private PropertyNamer() {
// Prevent Instantiation of Static Class
}

// 将方法名转换成属性名
public static String methodToProperty(String name) {
if (name.startsWith("is")) {
name = name.substring(2);
} else if (name.startsWith("get") || name.startsWith("set")) {
name = name.substring(3);
} else {
throw new ReflectionException("Error parsing property name '" + name + "'. Didn't start with 'is', 'get' or 'set'.");
}

if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
}

return name;
}

public static boolean isProperty(String name) {
return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
}

public static boolean isGetter(String name) {
return name.startsWith("get") || name.startsWith("is");
}

public static boolean isSetter(String name) {
return name.startsWith("set");
}

}

PropertyCopier

PropertyCopier是一个属性拷贝的工具类,核心方法是copyBeanProperties()方法,主要实现相同类型的两个对象之间的属性值拷贝,具体如下:

public final class PropertyCopier {

private PropertyCopier() {
// Prevent Instantiation of Static Class
}

public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
Class<?> parent = type;
while (parent != null) {
final Field[] fields = parent.getDeclaredFields();
for(Field field : fields) {
try {
field.setAccessible(true);
field.set(destinationBean, field.get(sourceBean));
} catch (Exception e) {
// Nothing useful to do, will only fail on final fields, which will be ignored.
}
}
// 继续拷贝父类中定义的字段
parent = parent.getSuperclass();
}
}

}

MetaClass

org.apache.ibatis.reflection.MetaClass ,类的元数据,基于 Reflector 和 PropertyTokenizer ,提供对指定类的各种骚操作。实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能。

代码:

public class MetaClass {

// 用于缓存Reflector对象
private final ReflectorFactory reflectorFactory;
// 创建MetaClass时会指定一个类,该Reflector对象会用于记录该类相关的元信息
private final Reflector reflector;

// MetaClass的构造方法是使用private修饰的
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
// 创建Reflector对象
this.reflector = reflectorFactory.findForClass(type);
}

// 使用静态方法创建MetaClass对象
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}

public MetaClass metaClassForProperty(String name) {
Class<?> propType = reflector.getGetterType(name);
return MetaClass.forClass(propType, reflectorFactory);
}

public String findProperty(String name) {
// 委托给buildProperty()方法实现
StringBuilder prop = buildProperty(name, new StringBuilder());
return prop.length() > 0 ? prop.toString() : null;
}

public String findProperty(String name, boolean useCamelCaseMapping) {
if (useCamelCaseMapping) {
name = name.replace("_", "");
}
return findProperty(name);
}

public String[] getGetterNames() {
return reflector.getGetablePropertyNames();
}

public String[] getSetterNames() {
return reflector.getSetablePropertyNames();
}

public Class<?> getSetterType(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaClass metaProp = metaClassForProperty(prop.getName());
return metaProp.getSetterType(prop.getChildren());
} else {
return reflector.getSetterType(prop.getName());
}
}

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);
}

private MetaClass metaClassForProperty(PropertyTokenizer prop) {
Class<?> propType = getGetterType(prop);
return MetaClass.forClass(propType, reflectorFactory);
}

private Class<?> getGetterType(PropertyTokenizer prop) {
Class<?> type = reflector.getGetterType(prop.getName());
if (prop.getIndex() != null && Collection.class.isAssignableFrom(type)) {
Type returnType = getGenericGetterType(prop.getName());
if (returnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
if (actualTypeArguments != null && actualTypeArguments.length == 1) {
returnType = actualTypeArguments[0];
if (returnType instanceof Class) {
type = (Class<?>) returnType;
} else if (returnType instanceof ParameterizedType) {
type = (Class<?>) ((ParameterizedType) returnType).getRawType();
}
}
}
}
return type;
}

private Type getGenericGetterType(String propertyName) {
try {
Invoker invoker = reflector.getGetInvoker(propertyName);
if (invoker instanceof MethodInvoker) {
Field _method = MethodInvoker.class.getDeclaredField("method");
_method.setAccessible(true);
Method method = (Method) _method.get(invoker);
return TypeParameterResolver.resolveReturnType(method, reflector.getType());
} else if (invoker instanceof GetFieldInvoker) {
Field _field = GetFieldInvoker.class.getDeclaredField("field");
_field.setAccessible(true);
Field field = (Field) _field.get(invoker);
return TypeParameterResolver.resolveFieldType(field, reflector.getType());
}
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
return null;
}

public boolean hasSetter(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
if (reflector.hasSetter(prop.getName())) {
MetaClass metaProp = metaClassForProperty(prop.getName());
return metaProp.hasSetter(prop.getChildren());
} else {
return false;
}
} else {
return reflector.hasSetter(prop.getName());
}
}

// 判断指定属性是否有 getting 方法
public boolean hasGetter(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
if (reflector.hasGetter(prop.getName())) {
MetaClass metaProp = metaClassForProperty(prop);
return metaProp.hasGetter(prop.getChildren());
} else {
return false;
}
} else {
return reflector.hasGetter(prop.getName());
}
}

public Invoker getGetInvoker(String name) {
return reflector.getGetInvoker(name);
}

public Invoker getSetInvoker(String name) {
return reflector.getSetInvoker(name);
}

private StringBuilder buildProperty(String name, StringBuilder builder) {
// 解析属性表达式
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) { // 是否还有子表达式
// 查找Propertytokenizer.name对应的属性
String propertyName = reflector.findPropertyName(prop.getName());
if (propertyName != null) {
builder.append(propertyName);
builder.append(".");
// 为该属性创建对应的MetaClass对象
MetaClass metaProp = metaClassForProperty(propertyName);
// 递归解析children字段,将解析结果添加到builder中保存
metaProp.buildProperty(prop.getChildren(), builder);
}
} else { // 递归出口
String propertyName = reflector.findPropertyName(name);
if (propertyName != null) {
builder.append(propertyName);
}
}
return builder;
}

public boolean hasDefaultConstructor() {
return reflector.hasDefaultConstructor();
}

}
  • MetaClass中比较重要的是findProperty(),它是通过调用MetaClass.buildProperty()方法实现的, 二buildProperty()方法会通过PropertyTokenizer解析复杂的属性表达式

ObjectWrapper

MetaClass是Mybatis对类级别的元信息的封装和处理,下面来看MyBatis对对象级别的元信息的处理。ObjectWrapper接口是对对象的包装,抽象了对象的属性信息,它定义了一系列查询对象属性信息的方法,以及更新属性的方法。

public interface ObjectWrapper {

// 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应属性的相应getter方法,
// 如果封装的是集合类,则获取指定key或下标对应的value值
Object get(PropertyTokenizer prop);

// 如果ObjectWrapper中封装的是普通的Bean对象,则调用相应的setter方法
// 如果封装的是集合类,则设置指定key或下标对应的value值
void set(PropertyTokenizer prop, Object value);

// 查找属性表达式指定的属性,第二个参数表示是否忽略属性表达式中的下划线
String findProperty(String name, boolean useCamelCaseMapping);

String[] getGetterNames();

String[] getSetterNames();

Class<?> getSetterType(String name);

Class<?> getGetterType(String name);

boolean hasSetter(String name);

boolean hasGetter(String name);

// 为属性表达式的属性创建相应的MetaObject对象
MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

boolean isCollection();

void add(Object element);

<E> void addAll(List<E> element);

}

ObjectWrapperFactory负责创建ObjectWrapper对象。

image-20200607105809062

DefaultObjectWrapperFactory实 现 了 ObjectWrapperFactory接口,但它 实 现 的 getWrapperFor() 方法始终 抛出异 常,hasWrapperFor()方法始终 返回false,所以该 实 现 实 际 上是不可用的。但是 与 ObjectFactory 类 似,我们 可以在 mybatis-config.xml 中配置自定义 的 ObjectWrapperFactory 实 现 类 进 行扩 展,在后面介绍 MyBatis初始化时 还 会 提到该 扩 展点。

BaseWrapper是一个实现了ObjectWrapper接口的抽象类,其中封装了MetaObject对象,并提供了三个方勇的方法提供其子类使用:

image-20200607110710849

BaseWrapper,resolveCollection()方法会 调 用 MetaObject.get Value()方法,它 会 解析属 性表达 式并 获 取指定的属 性。

BaseWrapper.getCollectionValue()方法和 setCollectionValue()方法会 解析属 性表达 式的索引 信息,然后获 取/设 置对 应 项 。

MetaObject

org.apache.ibatis.reflection.MetaObject ,对象元数据,提供了对象的属性值的获得和设置等等方法。 可以理解成,对 BaseWrapper 操作的进一步增强

ObjectWrapper提供了获取/设置对象中指定的属性值、检测getter/setter等常用功能,但是ObjectWrapper只是这些功能的最后一站,我们省略了对属性表达式解析过程的介绍,而该解析过程是在MetaObject中实现的。

MetaObject中字段的含义:

// 原始JavaBean对象
private final Object originalObject;

// 上文介绍的ObjectWrapper对象,其中封装了originalObject对象
private final ObjectWrapper objectWrapper;

// 负责实例化originalObject的工厂对象
private final ObjectFactory objectFactory;

// 负责创建ObjectWrapper的工厂对象
private final ObjectWrapperFactory objectWrapperFactory;

// 用于创建并缓存Reflector对象的工厂对象
private final ReflectorFactory reflectorFactory;

MetaObject的构造方法会根据传入的原始对象的类型以及ObjectFactory工厂的实现, 创建相应的ObjectWrapper对象:

private MetaObject(
Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;

if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}

MetaObject和ObjectWrapper中关于类级别的方法,例如hasGetter()、hasSetter()、findProperty()等方法,都是直接调用MetaClass的对应方法实现的。

其他方法都是关于对象级别的方法,这些方法都是与ObjectWrapper配合实现,例如MetaObject.getValue()/setValue()方法。

以下是getValue()的代码:

public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
// 根据PropertyTokenizer解析后指定的属性,创建相应的MetaObject对象
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}

类型转换

JDBC数据类型与Java语言中的数据类型并不是完全对应的,所以在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换成JDBC类型,而从结果集获取数据的时候,则需要从JDBC类型转换成Java类型。

MyBatis使用类型转换处理器完车过了上述两种转换。

image-20200607224820211

在 MyBatis中使用JdbcType这 个 枚举 类 型代表JDBC中的数 据类 型,该 枚举 类 型中定义 了 TYPE_CODE字段,记 录 了 JDBC类 型在java.sql.Types中相应 的常量 编 码 ,并 通过 一个 静 态 集合codeLookup (HashMap<Integer,JdbcType>类 型)维 护 了常量编 码 与JdbcType之间的对应关系。

TypeHandler

MyBatis中所有的类 型转 换 器都继 承了 TypeHandler接口,在 TypeHandler接口中定义 了如 下四个 方法,这 四个 方法分为 两 类 :setParameter()方法负 责 将 数 据由JdbcType类 型转 换 成Java 类 型:getResult()方法及其重载 负 责 将 数 据由Java类 型转 换 成JdbcType类 型。

public interface TypeHandler<T> {

//在通过 PreparedStatement为 SQL语 句绑 定参 数 时 ,会 将 数 据由JdbcType类 型转 换 成Java类 型
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

//从 ResultSet中获 取数 据时 会 调 用此方法,会 将 数 据由Java类 型转 换 成JdbcType类 型
T getResult(ResultSet rs, String columnName) throws SQLException;

T getResult(ResultSet rs, int columnIndex) throws SQLException;

T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

为了方便用户自定义TypeHandler实现,MyBatis提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference抽象类,其继承结构如下:

image-20200607225857225

在BaseTypeHandler中实现了TypeHandler.setParameter()方法和TypeHandler.getResult()方法,具体实现如下。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

protected Configuration configuration;

public void setConfiguration(Configuration c) {
this.configuration = c;
}

@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
"Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different configuration property. " +
"Cause: " + e, e);
}
}
}

@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
T result;
try {
result = getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
if (rs.wasNull()) {
return null;
} else {
return result;
}
}

@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
T result;
try {
result = getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from result set. Cause: " + e, e);
}
if (rs.wasNull()) {
return null;
} else {
return result;
}
}

@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
T result;
try {
result = getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex+ " from callable statement. Cause: " + e, e);
}
if (cs.wasNull()) {
return null;
} else {
return result;
}
}

public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

一般情况下, TypeHandler用于完成单个参数以及单个列值的类型转换, 如果存在多列值转换成一个Java对象的需求,应该优先考虑在使用映射文件中定义合适的映射规则()完成映射。

TypeHandlerRegistry

介绍完TypeHandler接口及其功能之后,MyBatis如何管理众多的TypeHanlder接口实现,如何知道何时使用哪个TypeHandler接口实现完成转换呢?

这个工作是由TypeHandlerRegistry完成的,在MyBatis初始化过程中,会为所有已知的TypeHanlder创建对象,并实现注册到TypeHandlerRegistry中, 由TypeHandlerRegistry负责管理这些TypeHandler对象。

TypeHandlerRegistry中核心字段的含义:

// 记录JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型,它定义对应的JDBC类型
// 该集合主要用于从结果集读取数据时,将数据从Jdbc类型转换成Java类型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP =
new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// 记录了Java类型向指定JdbcType转换时,需要使用的TypeHandler对象。
// 例如Java类型中的String可能转换成数据库的char、varchar等多种类型,所以存在一对多关系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP =
new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// 记录了全部TypeHandler的类型以及该类型相应的TypeHandler对象
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER =
new UnknownTypeHandler(this);

// 空TypeHandler集合的标识
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP =
new HashMap<Class<?>, TypeHandler<?>>();

1. 注册TypeHandler对象

TypeHandlerRegistry.register()方法实 现 了注册 TypeHandler对 象的功能,该 注册 过 程会 向上 述四个 集合中添加TypeHandler对 象。register()方法有多个 重载 ,这 些重载 之间 的调 用关 系如图 。

image-20200607231443963

从 上图中可以看出,多数 register()方法最终 会 调 用重载(4) 完成注册 功能,先来看该方法的实现,其三个 参 数 分别 指定了 TypeHandler能够 处 理的Java类 型、Jdbc类 型以及 TypeHandler对 象。

重载(4)

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {// 检测是否明确指定了TypeHanlder能够处理的Java类型
// 获取指定java类型在TYPE_HANDLER_MAP集合中对应TypeHanlder集合
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
// 创建新的TypeHandler集合,并添加到TYPE_HANDLER_MAP中
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
// 将TypeHandler对象注册到并添加到TYPE_HANDLER_MAP中
map.put(jdbcType, handler);
}
// 向ALL_TYPE_HANDLERS_MAP集合注册TypeHandler类型和对应的TypeHanlder对象
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

在(1)〜(3)这 三个 register()方法重载 中会 尝 试 读 取TypeHandler类 中定义 的@MappedTypes 注解和@MappedJdbcTypes注解,@MappedTypes注解用于指明该 TypeHandler实 现 类 能够 处 理 的Java类 型的集合,@MappedJdbcTypes注解用于指明该 TypeHandler实 现 类 能够 处 理的JDBC 类 型集合。register()方法的重载(1)〜(3)的具体 实 现 如下:

重载(1)

// Only handler type
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}

重载(2)

public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> handledType : mappedTypes.value()) {
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
// @since 3.1.0 - try to auto-discover the mapped type
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {
register((Class<T>) null, typeHandler);
}
}

重载(3)

private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}

上 述 全 部 的 register()方 法 重 载 都 是 在 向 TYPE_HANDLER_MAP集 合 和 ALL_TYPE_HANDLERS_MAP 集合注册 TypeHandler 对 象,而重载(5)是向 JDBC_TYPE_HANDLER_MAP 集合注册 TypeHandler对 象,其具体 实 现 如下:

重载(5)

public void register(JdbcType jdbcType, TypeHandler<?> handler) {
JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}

TypeHandlerRegistry除了提供注册 单 个 TypeHandler的register()重载 ,还 可以扫 描整个 包下 的TypeHandler接口实 现 类 ,并 将 完成这 些TypeHandler实 现 类 的注册 。

重载(6)

public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
// 查找指定包下的接口实现类
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
//Ignore inner classes and interfaces (including package-info.java) and abstract classes
if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
register(type);
}
}
}

2. 查找TypeHandler

TypeHandlerRegistry提供了查 找 TypeHandler对 象的功能。TypeHandlerRegistry.getTypeHandler()方法实 现 了从 上述四个 集合中获 取对 应 TypeHandler对 象的功能。TypeHandlerRegistry.getTypeHandler()方法有多个 重载 ,这 些重 载 之间 的关 系如下图所示。

image-20200608163219733

经 过 一系列类 型转 换 之后,TypeHandlerRegistry.getTypeHandler()方法的多个 重载 都会 调 用 TypeHandlerRegistry.getTypeHandle(Type, JdbcType)这 个 重载 方法,它 会 根据指定的 Java 类 型和 JdbcType类 型查 找 相应 的TypeHandler对 象。

TypeAliasRegistry

在编 写 SQL语 句时 ,使用别 名可以方便理解以及维 护 ,例如表名或列名很 长 时 ,我们 一般 会 为 其设 计 易懂 易维 护 的别 名。MyBatis将 SQL语 句中别 名的概 念进 行了延伸和扩 展,MyBatis 可以为 一个 类 添加一个 别 名,之后就可以通过 别 名引用该 类 。

MyBatis通过 TypeAliasRegistry类 完成别 名注册 和管理的功能,TypeAliasRegistry的结 构 比 较 简 单 ,它 通过 TYPE_ALIASES字段(Map<String,Class<?>>类 型)管理别 名与 Java类 型之间 的对 应 关 系,通过 TypeAHasRegistiy.registerAlias()方法完成注册 别 名,该 方法的具体 实 现 如下:

public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}

// 将别名转换成小写
String key = alias.toLowerCase(Locale.ENGLISH);

// 检测别名是否存在
if (TYPE_ALIASES.containsKey(key)
&& TYPE_ALIASES.get(key) != null
&& !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("...");
}
TYPE_ALIASES.put(key, value);
}

TypeAliasRegistry的构 造方法中,默认 为 Java的基本类 型及其数 组 类 型、基本类 型的封 装 类 及其数组类型、Date、BigDecimal、Biglnteger、Map、HashMap、List、ArrayList、Collection、 Iterator、ResultSet等类 型添加了别名。

参考

  • 《MyBatis技术内幕》

  • 部分图片来源——《MyBatis技术内幕》