Java泛型以及MyBatis泛型解析器的实现源码详解
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/49/ )。
在反射中,我们经常会遇到Type接口,它代表一个类型,位于“java.lang.reflect
”包内。该接口的源码如下所示,接口内只定义了一个方法。
public interface Type {
/**
* 获取类型的名称
*/
default String getTypeName() {
return toString();
}
}
Type接口及其子类的类图如图所示。
我们对Type接口的子类分别进行介绍。
- Class类:它代表运行的Java程序中的类和接口,枚举类型(属于类)、注解(属于接口)也都是Class类的子类。
- WildcardType接口:它代表通配符表达式。例如“?”、“? extends Number”、“? super Integer”都是通配符表达式。
- TypeVariable接口:它是类型变量的父接口。例如“Map
<K,V>
”中的“K”、“V”就是类型变量。 - ParameterizedType接口:它代表参数化的类型。例如“Collection
<String>
”就是参数化的类型。 - GenericArrayType接口:它代表包含ParameterizedType或者TypeVariable元素的列表。
在学习这些子类时,没有必要死记硬背。只要有个大体的印象,遇到时直接通过开发工具跳转到源代码处查看定义即可,下图展示了WildcardType接口上的原生注释。
遇到不了解的类、方法时,直接跳转到类、方法的定义处查看其原生注释是学习Java编程、阅读项目源码时非常有效的方法。(均参考自书籍《通用源码阅读指导书——MyBatis源码详解》)
但是,泛型的解析并不简单,因为它指代的类是不确定的,是一个范围。接下来,我们通过MyBatis的源码学习如何进行泛型的解析。
MyBatis的泛型解析由TypeParameterResolver实现,我们称它为泛型参数解析器。
在阅读它的源代码之前我们先弄清一个问题:它的功能是什么?很多情况下,弄清一个类的功能对阅读它的源代码十分必要。
试想我们有User和Student两个类,分别如下所示。
public class User<T> {
public List<T> getInfo() {
return null;
}
}
public class Student extends User<Number> {
}
请问:Student类中的getInfo方法(继承自父类User)的输出参数类型是什么?
答案很简单,是“List<Number>
”。但是这个得出这个答案的过程却涉及到User和Student两个类。首先通过User类确定getInfo方法的输出结果是“List<T>
”,然后通过Student类得知“T”被设置为“Number”。因此,Student类中的getInfo方法的输出参数是“List<Number>
”。
TypeParameterResolver类的功能就是完成上述分析过程,帮助MyBatis推断出属性、返回值、入参中泛型的具体类型。例如通过下面所示的调用,TypeParameterResolver便分析出User类中的getInfo方法的输出参数是“List<Object>
”,Student类中的getInfo方法的输出参数是“List<Number>
”。
// 使用TypeParameterResolver分析User类中getInfo方法输出结果的具体类型
Type type1 = TypeParameterResolver.resolveReturnType(User.class.getMethod("getInfo"), User.class);
System.out.println("User类中getInfo方法的输出结果类型 :\n" + type1);
// 使用TypeParameterResolver分析Student类中getInfo方法输出结果的具体类型
Type type2 = TypeParameterResolver.resolveReturnType(User.class.getMethod("getInfo"), Student.class);
System.out.println("Student类中getInfo方法的输出结果类型 :\n" + type2);
TypeParameterResolver类分析出的结果如图所示。
了解了TypeParameterResolver类的功能后,我们来查看它的源代码。它一共对外提供三个方法:
- resolveFieldType:解析属性的泛型
- resolveReturnType:解析方法返回值的泛型
- resolveParamTypes:解析方法入参的泛型
上述这三个方法都只是将要解析的变量从属性、方法返回值、方法入参中找出来。变量的泛型解析才是最核心的工作。以代码所示的resolveParamTypes方法为例,该方法将变量从方法入参中找出后,对每个变量都调用了resolveType方法。因此resolveType是最重要的方法。
/**
* 解析方法入参
* @param method 目标方法
* @param srcType 目标方法所属的类
* @return 解析结果
*/
public static Type[] resolveParamTypes(Method method, Type srcType) {
// 取出方法的所有入参
Type[] paramTypes = method.getGenericParameterTypes();
// 定义目标方法的类或接口
Class<?> declaringClass = method.getDeclaringClass();
// 解析结果
Type[] result = new Type[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
// 对每个入参依次调用resolveType方法
result[i] = resolveType(paramTypes[i], srcType, declaringClass);
}
return result;
}
resolveType方法根据目标类型的不同调用不同的子方法进行了处理。
在分析resolveType方法的源码之前,有必要再强调一下resolveType的入参以防止大家混淆。以我们上文中提到的“Student类中的getInfo方法(继承自父类User)的输出参数类型是什么?”这一问题为例,则:
- type:指我们要分析的字段或者参数的类型。这里我们要分析的是getInfo的输出参数,即“
List<T>
”的类型。 - srcType:指我们要分析的字段或者参数所属的类。我们这里要分析的是Student类中的getInfo方法,故所属的类是Student类。
- declaringClass:指定义我们要分析的字段或者参数的类。getInfo方法在User类中被定义,故这里是User类。
resolveType方法的带注释源码如代码所示。
/**
* 解析变量的实际类型
* @param type 变量的类型
* @param srcType 变量所属于的类
* @param declaringClass 定义变量的类
* @return 解析结果
*/
private static Type resolveType(Type type, Type srcType, Class<?> declaringClass) {
if (type instanceof TypeVariable) { // 如果是类型变量,例如“Map<K,V>”中的“K”、“V”就是类型变量。
return resolveTypeVar((TypeVariable<?>) type, srcType, declaringClass);
} else if (type instanceof ParameterizedType) { // 如果是参数化类型,例如“Collection<String>”就是参数化的类型。
return resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);
} else if (type instanceof GenericArrayType) { // 如果是包含ParameterizedType或者TypeVariable元素的列表
return resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);
} else {
return type;
}
}
resolveType根据不同的参数类型调用了不同的子方法进行处理。我们直接以“List<T>
”对应的resolveParameterizedType子方法为例进行分析。而resolveParameterizedType子方法也是所有子方法中最为复杂的一个。
“List<T>
”作为参数化类型会触发resolveParameterizedType方法进行处理。resolveParameterizedType方法的带注释源码如代码所示。
/**
* 解析参数化类型的实际结果
* @param parameterizedType 参数化类型的变量
* @param srcType 该变量所属于的类
* @param declaringClass 定义该变量的类
* @return 参数化类型的实际结果
*/
private static ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Type srcType, Class<?> declaringClass) {
// 变量的原始类型。本示例中为List
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
// 获取类型参数。本示例中只有一个类型参数T
Type[] typeArgs = parameterizedType.getActualTypeArguments();
// 类型参数的实际类型
Type[] args = new Type[typeArgs.length];
for (int i = 0; i < typeArgs.length; i++) { // 依次处理每一个类型参数
if (typeArgs[i] instanceof TypeVariable) { // 类型参数是类型变量。例如parameterizedType为List<T>则属于这种情况
args[i] = resolveTypeVar((TypeVariable<?>) typeArgs[i], srcType, declaringClass);
} else if (typeArgs[i] instanceof ParameterizedType) { // 类型参数是参数化类型。例如parameterizedType为List<List<T>>则属于这种情况
args[i] = resolveParameterizedType((ParameterizedType) typeArgs[i], srcType, declaringClass);
} else if (typeArgs[i] instanceof WildcardType) { // 类型参数是通配符泛型。例如parameterizedType为List<? extends Number>则属于这种情况
args[i] = resolveWildcardType((WildcardType) typeArgs[i], srcType, declaringClass);
} else { // 类型参数是确定的类型。例如parameterizedType为List<String>则属于这种情况
args[i] = typeArgs[i];
}
}
return new ParameterizedTypeImpl(rawType, null, args);
}
对于resolveParameterizedType方法中的各种分支情况我们已经在代码6-36中通过注释进行了详细说明。在我们的示例中,parameterizedType为“List<T>
”,因此会继续调用resolveTypeVar方法对泛型变量“T”进行进一步的解析。
resolveTypeVar方法的带注释源码如下面代码所示。resolveTypeVar方法会尝试通过继承关系等确定泛型变量的具体结果。
/**
* 解析泛型变量的实际结果
* @param typeVar 泛型变量
* @param srcType 该变量所属于的类
* @param declaringClass 定义该变量的类
* @return 泛型变量的实际结果
*/
private static Type resolveTypeVar(TypeVariable<?> typeVar, Type srcType, Class<?> declaringClass) {
// 解析出的泛型变量的结果
Type result;
Class<?> clazz;
if (srcType instanceof Class) { // 该变量属于确定的类。该示例中,变量T属于Student类,Student类是一个确定的类
clazz = (Class<?>) srcType;
} else if (srcType instanceof ParameterizedType) { // 该变量属于参数化类型
ParameterizedType parameterizedType = (ParameterizedType) srcType;
// 获取参数化类型的原始类型
clazz = (Class<?>) parameterizedType.getRawType();
} else {
throw new IllegalArgumentException("The 2nd arg must be Class or ParameterizedType, but was: " + srcType.getClass());
}
if (clazz == declaringClass) { // 变量属于的类和定义变量的类一致。该示例中,变量T属于Student,定义于User
// 确定泛型变量的上届
Type[] bounds = typeVar.getBounds();
if (bounds.length > 0) {
return bounds[0];
}
// 泛型变量无上届,则上届为Object
return Object.class;
}
// 获取变量属于的类的父类。在该示例中,变量属于Student类,其父类为User<Number>类
Type superclass = clazz.getGenericSuperclass();
// 扫描父类,查看能否确定边界。该示例中,能确定出边界为Number
result = scanSuperTypes(typeVar, srcType, declaringClass, clazz, superclass);
if (result != null) {
return result;
}
// 获取变量属于的类的接口
Type[] superInterfaces = clazz.getGenericInterfaces();
// 依次扫描各个父接口,查看能否确定边界。该示例中,Student类无父接口
for (Type superInterface : superInterfaces) {
result = scanSuperTypes(typeVar, srcType, declaringClass, clazz, superInterface);
if (result != null) {
return result;
}
}
// 如果始终找不到结果,则未定义。即为Object
return Object.class;
}
这样,我们以“Student类中的getInfo方法(继承自父类User)的输出参数类型是什么?”这一问题为主线,对TypeParameterResolver的源码进行了阅读。(均参考自书籍《通用源码阅读指导书——MyBatis源码详解》)
resolveType方法中,会根据变量的类型调用resolveTypeVar、resolveParameterizedType、resolveGenericArrayType三个方法进行解析。而在本节中,我们通过示例List<T>
”对resolveTypeVar、resolveParameterizedType的源码进行了阅读。而resolveGenericArrayType方法的带注释源码如下面代码所示。
/**
* 解析泛型列表的实际类型
* @param genericArrayType 泛型列表变量类型
* @param srcType 变量所属于的类
* @param declaringClass 定义变量的类
* @return 解析结果
*/
private static Type resolveGenericArrayType(GenericArrayType genericArrayType, Type srcType, Class<?> declaringClass) {
Type componentType = genericArrayType.getGenericComponentType();
Type resolvedComponentType = null;
if (componentType instanceof TypeVariable) { // 元素类型是类变量。例如genericArrayType为T[]则属于这种情况
resolvedComponentType = resolveTypeVar((TypeVariable<?>) componentType, srcType, declaringClass);
} else if (componentType instanceof GenericArrayType) { // 元素类型是泛型列表。例如genericArrayType为T[][]则属于这种情况
resolvedComponentType = resolveGenericArrayType((GenericArrayType) componentType, srcType, declaringClass);
} else if (componentType instanceof ParameterizedType) { // 元素类型是参数化类型。例如genericArrayType为Collection<T>[]则属于这种情况
resolvedComponentType = resolveParameterizedType((ParameterizedType) componentType, srcType, declaringClass);
}
if (resolvedComponentType instanceof Class) {
return Array.newInstance((Class<?>) resolvedComponentType, 0).getClass();
} else {
return new GenericArrayTypeImpl(resolvedComponentType);
}
}
resolveGenericArrayType方法并不复杂,只是根据元素类型又调用了其他几个方法。
这样,我们断点调试法为基础,以“List<T>
”类型的泛型变量为用例,用以点带面的方式完成了TypeParameterResolver类的源码阅读。这种以用例为主线的源码阅读方法能帮助我们排除很多干扰从而专注于一条逻辑主线。而等这条逻辑主线的源码被阅读清楚时,其他逻辑主线往往也会迎刃而解。
关于泛型使用与泛型解析的更多知识,大家参阅《通用源码阅读指导书——MyBatis源码详解》。
作为一个ORM框架,MyBatis中往往不能知道要增删改查的目标对象的具体类型,因此泛型是十分常用的。
《通用源码阅读指导书——MyBatis源码详解》是一本以MyBatis的源码为实例讲述源码阅读方法的书籍,并且附带有示例项目源码,MyBatis的全中文注解。书籍还总结了大量的编程知识和架构经验,对提升编程和架构能力十分有用,非常推荐。
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。