1.文章来源
本系列文章是我从《通用源码指导书:MyBatis源码详解》一书中的笔记和总结
本书是基于MyBatis-3.5.2版本,书作者 易哥 链接里是优快云中易哥的微博。但是翻看了所有文章里只有一篇简单的介绍这本书。并没有过多的展示该书的魅力。接下来我将自己的学习总结记录下来。如果作者认为我侵权请联系删除,再次感谢易哥提供学习素材。本段说明将伴随整个系列文章,尊重原创,本人已在微信读书购买改书。
版权声明:本文为优快云博主「架构师易哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/onlinedct/article/details/107306041
2.包结构
按照功能,将包分为三大类:
- 基础功能包
这些包用来为其他包提供一些外围的基础功能,如:文件读取、反射、日志等。这些包中的方法比较独立,与业务逻辑耦合小。了解这些包可以在日常开发中当做工具类库使用。 - 配置解析包
这些包用来完成配置解析和存储工作,主要在系统初始化加载配置时运行。 - 核心操作包
这些包是MyBatis核心,完成数据库操作主要依赖这些包
书中提供了一个比较全面的架构图来描述这些包的分类
源码阅读过程中非常重要的技巧 先从外围源码入手,因为外围源码很少依赖核心源码。核心源码大量依赖外围源码,在阅读核心代码时
3.exception包
有三个Exception相关的类,IbatisException,PersistenceException,TooManyResultsException。其它包中还有许多异常类,都是PersistenceException子类。
通过 MyBatis异常类的类图还可以看出,众多的异常类并没有放在exceptions包中,而是散落在其他各个包中。这涉及项目规划时的分包问题。通常,在规划一个项目的包结构时,可以按照以下两种方式进行包的划分。
- 按照类型方式划分,例如将所有的接口类放入一个包,将所有的 Controller类放入一个包。这种分类方式从类型上看更为清晰,但是会将完成同一功能的多个类分散在不同的包中,不便于模块化开发。
- 按照功能方式划分,例如将所有与加/解密有关的类放入一个包,将所有与HTTP请求有关的类放入一个包。这种分类方式下,同一功能的类内聚性高,便于模块化开发,但会导致同一包内类的类型混乱。
那么在我们开发时改如何选择呢?推荐先将功能耦合度高的类放入按照功能划分,功能耦合度低或者供多个模块使用的类按照类型划分。
3.1 ExceptionFactory类
看名字也知道了是异常工厂类。改类只有两个方法
public class ExceptionFactory {
// 不允许实例化该类
private ExceptionFactory() {
// Prevent Instantiation
}
/**
* 生成一个RuntimeException异常
* @param message 异常信息
* @param e 异常
* @return 新的RuntimeException异常
*/
public static RuntimeException wrapException(String message, Exception e) {
return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
}
}
4.reflection包
reflection包提供了强大的反射基础包。与业务代码耦合度低,可作工具包使用。
4.1对象工厂子包
4.1.1 factory子包是一个对象工厂子包,用来基于反射的各种对象。
ObjectFactory接口方法介绍:
- default void setProperties(Properties properties) :用来设置工厂的属性。
- T create(Class type) : 使用改类型的无参构造方法创建传入类型的实例。
- T create(Class type, List<Class<?>> constructorArgTypes, List constructorArgs): 传入类型,构造方法参数类型,构造方法参数。创建该类型实例
- boolean isCollection(Class type):判断是否是集合类
4.1.2 DefaultObjectFactory
DefaultObjectFactory是ObjectFactory的默认实现。
两个create方法最终都调用了instantiateClass()方法。
/**
* 创建类的实例
* @param type 要创建实例的类
* @param constructorArgTypes 构造方法入参类型
* @param constructorArgs 构造方法入参
* @param <T> 实例类型
* @return 创建的实例
*/
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
// 构造方法
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) { // 参数类型列表为null或者参数列表为null
// 因此获取无参构造函数
constructor = type.getDeclaredConstructor();
try {
// 使用无参构造函数创建对象
return constructor.newInstance();
} catch (IllegalAccessException e) {
// 如果发生异常,则修改构造函数的访问属性后再次尝试
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance();
} else {
throw e;
}
}
}
// 根据入参类型查找对应的构造器
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
try {
// 采用有参构造函数创建实例
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
// 如果发生异常,则修改构造函数的访问属性后再次尝试
constructor.setAccessible(true);
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} else {
throw e;
}
}
} catch (Exception e) {
// 收集所有的参数类型
String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
.stream().map(Class::getSimpleName).collect(Collectors.joining(","));
// 收集所有的参数
String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
.stream().map(String::valueOf).collect(Collectors.joining(","));
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}
4.2invoker包
invoker包是执行器字包,该子包中的类能够基于反射实现对象方法的调用和对象属性的读写。
Invoker接口有两个方法:
// 方法执行调用器
Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException;
// 传入参数或者传出参数的类型(如有一个入参就是入参,否则是出参)
Class<?> getType();
有一个接口和三个实现类:
- GetFieldInvoker:对象属性的读操作
- MethodInvoker:对象方法操作
- SetFieldInvoker:属性写操作
4.3property包
该包中的类是用来完成与对象的属性操作。共有三个类。
- PropertyCopier :属性拷贝
工作原理非常简单,通过反射获取类的所有属性,然后依次获取改属性的值并赋值给目标对象。注意改属性复制器无法完成继承属性的复制。因为获取属性的方法是getDeclaredFields(),该方法的返回不包含继承属性。 - PropertyNamer:属性名称相关操作。例如:通过get、set方法名找出对应的属性。但是需要包装对象属性、方法的命名遵循Java Bean的命名规范。
- PropertyTokenizer:属性标记:传入一个"student[id].name"字符串后,将其拆开放入到各个属性中。用来解析入参属性。
4.4wrapper包
对象包装器子包。该子包中的类使用装饰器模式对各种类型的对象进行进一步的封装,增加一些功能。
ObjectWrapperFactory 是对象包装器工厂的接口,DefaultObjectWrapperFactory 是它的默认实现。不过该默认实现中并没有实现任何功能。MyBatis 也允许用户通过配置文件中的 objectWrapperFactory节点来注入新的ObjectWrapperFactory。
ObjectWrapper接口是所有对象包装器的总接口。以 BeanWrapper为例,介绍一下包装器的实现。在介绍之前先了解 reflection包中的两个类:MetaObject类和MetaClass类。
meta 在中文中常译为“元”,在英文单词中作为词头有“涵盖”“超越”“变换”等多种含义。在这里,这三种含义都是存在的。例如,MetaObject类中涵盖了对应 Object类中的全部信息,并经过变化和拆解得到了一些更为细节的信息。因此,可以将MetaObject类理解为一个涵盖对象(Object)中更多细节信息和功能的类,称为“元对象”。同理,MetaClass就是一个涵盖了类型(Class)中更多细节信息和功能的类,称为“元类”。
BeanWrapper中包含了一个 Bean的对象信息、类型信息,并提供了更多的一些功能。BeanWrapper中存在的方法有:
- get:获得被包装对象某个属性的值;
- set:设置被包装对象某个属性的值;
- findProperty:找到对应的属性名称;
- getGetterNames:获得所有的属性 get方法名称;
- getSetterNames:获得所有的属性 set方法名称;
- getSetterType:获得指定属性的 set方法的类型;
- getGetterType:获得指定属性的 get方法的类型;
- hasSetter:判断某个属性是否有对应的 set方法;
- hasGetter:判断某个属性是否有对应的 get方法;
- instantiatePropertyValue:实例化某个属性的值。
一个 Bean经过 BeanWrapper封装后,就可以暴露出大量的易用方法,从而可以简单地实现对其属性、方法的操作。同理,wrapper子包下的 CollectionWrapper、MapWrapper与BeanWrapper一样,它们分别负责包装 Collection和 Map类型,从而使它们暴露出更多的易用方法。
BaseWrapper作为 BeanWrapper和 MapWrapper的父类,为这两个类提供一些共用的基础方法。
源码阅读时,遇到同类型的类(一般具有类似的名称、功能),可以重点阅读其中的一个类。当这个类的源码阅读清楚时,同类型类的源码也就清晰了。
4.5Reflector类
reflector包中最为核心的类就是Reflector类。Reflector类负责对一个类进行反射解析,并将解析后的结果在属性中存储起来。Reflector类完成了主要的反射解析工作。
主要属性有:
// 要被反射解析的类
private final Class<?> type;
// 能够读的属性列表,即有get方法的属性列表
private final String[] readablePropertyNames;
// 能够写的属性列表,即有set方法的属性列表
private final String[] writablePropertyNames;
// set方法映射表。键为属性名,值为对应的set方法
private final Map<String, Invoker> setMethods = new HashMap<>();
// get方法映射表。键为属性名,值为对应的get方法
private final Map<String, Invoker> getMethods = new HashMap<>();
// set方法输入类型。键为属性名,值为对应的该属性的set方法的类型(实际为set方法的第一个参数的类型)
private final Map<String, Class<?>> setTypes = new HashMap<>();
// get方法输出类型。键为属性名,值为对应的该属性的set方法的类型(实际为set方法的返回值类型)
private final Map<String, Class<?>> getTypes = new HashMap<>();
// 默认构造函数
private Constructor<?> defaultConstructor;
// 大小写无关的属性映射表。键为属性名全大写值,值为属性名
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
ReflectorFactory是Reflector的工厂接口,而DefaultReflectorFactory是该工厂接口的默认实现。DefaultReflectorFactory中核心的方法是用来生成一个类的Reflector对象。因反射效率比较低DefaultReflectorFactory中还加入了缓存来提高效率
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) { // 允许缓存
// 生产入参type的反射器对象,并放入缓存
return reflectorMap.computeIfAbsent(type, Reflector::new);
} else {
return new Reflector(type);
}
}
4.6ExceptionUtil
ExceptionUtil 是一个异常工具类,它提供一个拆包异常的工具方法 unwrapThrowable。该方法将InvocationTargetException 和UndeclaredThrowableException 这两类异常进行拆包,得到其中包含的真正的异常。
public static Throwable unwrapThrowable(Throwable wrapped) {
// 该变量用以存放拆包得到的异常
Throwable unwrapped = wrapped;
while (true) {
if (unwrapped instanceof InvocationTargetException) {
// 拆包获得内部异常
unwrapped = ((InvocationTargetException) unwrapped).getTargetException();
} else if (unwrapped instanceof UndeclaredThrowableException) {
// 拆包获得内部异常
unwrapped = ((UndeclaredThrowableException) unwrapped).getUndeclaredThrowable();
} else {
// 该异常无需拆包
return unwrapped;
}
}
}
unwrapThrowable 方法的结构非常简单,但是我们需要思考它存在的意义:为什么需要给 InvocationTargetException和UndeclaredThrowableException这两个类拆包?这两个类为什么要把其他异常包装起来?
很多时候读懂源码的实现并不难,但是一定要多思考源码为什么这么写。只有这样,才能在源码阅读的过程中有更多的收获。
要解答上述问题先从了解 InvocationTargetException和UndeclaredThrowableException这两个类开始。
InvocationTargetException为必检异常,UndeclaredThrowableException为免检的运行时异常。它们都不属于 MyBatis,而是来自 java.lang.reflect包。
反射操作中,代理类通过反射调用目标类的方法时,目标类的方法可能抛出异常。反射可以调用各种目标方法,因此目标方法抛出的异常是多种多样无法确定的。这意味着反射操作可能抛出一个任意类型的异常。可以用 Throwable 去接收这个异常,但这无疑太过宽泛。InvocationTargetException就是为解决这个问题而设计的,当反射操作的目标方法中出现异常时,都统一包装成一个必检异常 InvocationTargetException。InvocationTargetException内部的 target 属性则保存了原始的异常。这样一来,便使得反射操作中的异常更易管理。
根据 Java的继承原则,我们知道:如果子类中要重写父类中的方法,那么子类方法中抛出的必检异常必须是父类方法中声明过的类型。在建立目标类的代理类时,通常是建立了目标类接口的子类或者目标类的子类(10.1.3节和 22.1.1节会详细介绍动态代理)。
因此,将 Java的继承原则放在代理类和被代理类上可以演化为:
- 如果代理类和被代理类实现了共同的接口,则代理类方法中抛出的必检异常必须是在共同接口中声明过的;
- 如果代理类是被代理类的子类,则代理类方法中抛出的必检异常必须是在被代理类的方法中声明过的。
可是在代理类中难免会在执行某些方法时抛出一些共同接口或者父类方法中没有声明的必检异常,那这个问题该怎么解决呢?如果不抛出,则它是必检异常,必须抛出;如果抛出,则父接口或父类中没有声明该必检异常,不能抛出。答案就是这些必检异常会被包装为免检异常UndeclaredThrowableException 后抛出。所以说UndeclaredThrowableException 也是一个包装了其他异常的异常
public class UndeclaredThrowableException extends RuntimeException {
/**
* Constructs an {@code UndeclaredThrowableException} with the
* specified {@code Throwable}.
*
* @param undeclaredThrowable 被包装的必检异常 the undeclared checked exception
* that was thrown
*/
public UndeclaredThrowableException(Throwable undeclaredThrowable) {
super(null, undeclaredThrowable); // Disallow initCause
}
}
有一个简单的例子可以恰好同时涉及InvocationTargetException 和 Undeclared-hrowablexception 这两个异常。就是代理类在进行反射操作时发生异常,于是异常被包装成 InvocationTargetException。InvocationTargetException显然没有在共同接口或者父类方法中声明过,于是又被包装成了UndeclaredThrowableException。这样,真正的异常就被包装了两层。这也是为什么在ExceptionUtil的unwrapThrowable方法中存在一个“while (true)”死循环,用来持续拆包。
4.7ParamNameResolver参数名称解析器
ParamNameResolver 类主要的方法有两个:构造方法ParamNameResolver 和getNamedParams方法。
- 构造方法 ParamNameResolver能够将目标方法的参数名称依次列举出来。最终生成方法入参的参数次序表。 在列举的过程中,如果某个参数存在@Param注解,则会用注解的 value值替换参数名。
- getNamedParams 获取参数名称:如果只有一个参数,直接返回参数。如果有多个参数则返回参数位置和参数对象Map
4.8 TypeParameterResolver是泛型参数解析器
在阅读源码前,理解这个类的功能对阅读源码十分必要。
那么泛型参数解析器的功能是什么呢?先看代码
public class User<T> {
public List<T> getInfo() {
return null;
}
}
public class Student extends User<Number> {
}
在Student类中的getInfo()方法的出参类型是什么?当然答案很简单是”Number“,但是得出这个答案的过程涉及到User和Student两个类,首先通过User确定getInfo方法的输出参数是 List,然后要通过Student得知T被设置为Number。
TypeParameterResolver 类的功能就是完成上述分析过程,帮助 MyBatis 推断出属性、返回值、输入参数中泛型的具体类型。
测试代码:
public static void main(String[] args) {
try {
// 使用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);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
运行结果:
User类中getInfo方法的输出结果类型 :
ParameterizedTypeImpl [rawType=interface java.util.List, ownerType=null, actualTypeArguments=[class java.lang.Object]]
Student类中getInfo方法的输出结果类型 :
ParameterizedTypeImpl [rawType=interface java.util.List, ownerType=null, actualTypeArguments=[class java.lang.Number]]
*/
了解了TypeParameterResolver类的功能后,看他的三个方法:
- resolveFieldType :解析属性的泛型
- resolveReturnType:解析方法返回值的泛型
- resolveParamTypes:解析方法输入参数的泛型
三个方法都调用了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的入参:
- type:指要分析的字段或者参数的类型。这里我们要分析的是getInfo的输出参数,即“List”的类型。
- srcType:指要分析的字段或者参数所属的类。我们这里要分析的是 Student类中的getInfo方法,故所属的类是 Student类。
- declaringClass:指定义要分析的字段或者参数的类。getInfo 方法在 User类中被定义,故这里是 User类。
“List”作为参数化类型会触发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);
}
resolveTypeVar方法对泛型变量“T”进行进一步的解析
/**
* 解析泛型变量的实际结果
* @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;
}
以用例为主线的源码阅读方法能帮助我们排除很多干扰从而专注于一条逻辑主线。而等这条逻辑主线的源码被阅读清楚时,其他逻辑主线往往也会迎刃而解。