mybatis源码详解:reflection包源码阅读
reflection包reflection包是提供反射功能的基础包。该包功能强大且与 MyBatis的业务代码耦合度低,可以直接复制到其他项目中使用。
6.1 背景知识
装饰器模式
装饰器模式又称包装模式,是一种结构型模式。这种设计模式是指能够在一个类的基础上增长一个装饰类(也可以叫包装类),并在装饰类中增长一些新的特性和功能。这样,通过对原有类的包装,就可以在不改变原有类的情况下为原有类增长更多的功能。
例如,界说如代码6-1所示的 Phone接口,它规定了发送和吸收语音的抽象方法。
【代码6-1】
https://p1.pstatp.com/large/pgc-image/98478bb238c84b718b8d931060b6a2b9
然后界说一个类 TelePhone,实现 Phone 接口,能够实现打电话的功能,如代码6-2所示。
【代码6-2】
https://p1.pstatp.com/large/pgc-image/514e5cf4c6694d5f99f79af915af26e1
如今要创建一个装饰类,在不改变原有 TelePhone 的基础上,实现通话灌音功能。装饰类的源码如代码6-3所示。
【代码6-3】
https://p3.pstatp.com/large/pgc-image/ab43502042814d60aaf84039d69c3b2c
https://p1.pstatp.com/large/pgc-image/e66a301f033f4a3faf12683868c9779f
这样,经过 PhoneRecordDecorator包装过的 Phone就具有了通话灌音功能,如代码6-4所示。
【代码6-4】
https://p3.pstatp.com/large/pgc-image/7349aabcc15f460db1e74f8b3b39698e
步伐运行结果如图6-1所示。
https://p3.pstatp.com/large/pgc-image/60c61df99c724289b434a7b230ab1eca图6-1 步伐运行结果
本示例中,我们使用装饰器模式对被包装类的功能进行了扩展,但是不影响原有类。遵照这个头脑,还可以通过装饰类增长新的方法、属性等。例如,我们给原来的 TelePhone类增长收发短信功能,如代码6-5所示。
【代码6-5】
https://p3.pstatp.com/large/pgc-image/a5759f2ea31d4e219bbf116159a77f7b
装饰器模式在编程开发中经常使用。通常的使用场景是在一个核心基本类的基础上,提供大量的装饰类,从而使核心基本类经过差别的装饰类修饰后得到差别的功能。
装饰类另有一个长处,就是可以叠加使用,即一个核心基本类可以被多个装饰类修饰,从而同时具有多个装饰类的功能。
反射
通过 Java反射,能够在类的运行过程中知道这个类有哪些属性和方法,还可以修改属性、调用方法、建立类的实例。
下面假设有一个如代码6-6所示的类。
【代码6-6】
https://p3.pstatp.com/large/pgc-image/d3f7ed24e9794095a9ee7e30c774440c
在编程时可以使用代码6-7为对象赋值。
【代码6-7】
https://p3.pstatp.com/large/pgc-image/a4f487f246874f7fbf40d29b01732694
我们之以是能调用 User 实例的 setName 方法,是由于编译器通过分析源码知道 User中确实存在一个 setName方法。要实现一个对象比较功能,比较两个 User对象的属性有什么差别,则可以通过代码6-8实现。
【代码6-8】
https://p3.pstatp.com/large/pgc-image/e16c15b9f1bc44468008586c8b7b13bf
在代码6-8所示的 diffUser方法中,我们在编码时就知道 User对象有哪些属性、方法,然后轮番比较即可。因此,该 diffUser方法只能比较两个 User对象的差别,而无法比较其他类的对象。
那如何修改才气使得该比较方法适用于任何类的对象呢?我们面对两个问题:
· 不知道所要传入的对象的具体类型;
· 由于传入对象的类型未知,那么对象的属性也是未知的。
要解决上述两个问题,必要在系统运行阶段,准确地说是在参数传入后,直接判断传入对象的类型及其包含的属性和方法。这时,反射就派上用场了。
Java反射机制主要提供了以下功能。
· 在运行时判断任意一个对象所属的类;
· 在运行时构造任意一个类的对象;
· 在运行时修改任意一个对象的成员变量;
· 在运行时调用任意一个对象的方法。
于是,我们可以先通过反射获取对象的类,从而判断两个对象是否属于同一个类;然后获取对象的成员变量,轮番比较两个对象的成员变量是否划一。最终,我们将功能改写为代码6-9所示的形式。
【代码6-9】
https://p3.pstatp.com/large/pgc-image/6e47d6d5d7b743d9a50f6e9a7e290c0f
这样,diffObj方法可以比较任意类型对象的属性差别。例如,我们给出代码6-10所示的测试代码,使用 diffObj方法分别比较 User对象和 Book对象的差别,即可得到图6-2所示的步伐运行结果。
【代码6-10】
https://p1.pstatp.com/large/pgc-image/3980c25243ae45b1ad2b4a83581bb106
https://p1.pstatp.com/large/pgc-image/2b509c772b0b49d59943a74fe34f655b图6-2 步伐运行结果
在代码6-10中调用代码6-9所示的 diffObj方法完成了 User和 Book两个完全差别类的对象属性对比工作。因此,反射极大地提升了 Java的机动性,降低了 diffObj 方法和输入参数的耦合,使功能更为通用。
https://p3.pstatp.com/large/pgc-image/6293365f65684ca69b2e81a0278bdf89
https://p1.pstatp.com/large/pgc-image/fafcf33b900e44c79f547255bbd57f2e
https://p1.pstatp.com/large/pgc-image/749e8bf8d1124d49910ab465df2f6a96
Type接口及其子类
在反射中,我们经常会遇到 Type接口,它代表一个类型,位于“java.lang.reflect”包内。该接口的源码如代码6-11所示,接口内只界说了一个方法。
【代码6-11】
https://p1.pstatp.com/large/pgc-image/cc463aff9a304bf6b4d93def8a661251
Type接口及其子类类图如图6-4所示。
https://p3.pstatp.com/large/pgc-image/51aaa38bbd1d43b9a1ebfc72c6682abd图6-4 Type接口及其子类类图
我们对 Type接口的子类分别进行介绍。
· Class类:它代表运行的 Java步伐中的类和接口,枚举类型(属于类)、注解(属于接口)也都是 Class类的子类。
· WildcardType 接口:它代表通配符表达式。例如,“?”“?extends Number”“?super Integer”都是通配符表达式。
· TypeVariable 接口:它是类型变量的父接口。例如,“Map<K,V>”中的“K”“V”就是类型变量。
· ParameterizedType 接口:它代表参数化的类型。例如,“Collection <String>”就是参数化的类型。· GenericArrayType接口:它代表包含 ParameterizedType或者TypeVariable元素的列表。
在学习这些子类时,没有必要死记硬背,只要有个大体的印象,遇到时直接通过开发工具跳转到源代码处查看界说即可。图6-5展示了 WildcardType接口上的原生注释。
https://p1.pstatp.com/large/pgc-image/4370167bdf94413caf8ed05d8689023d图6-5 WildcardType接口上的原生注释
遇到不了解的类、方法时,直接跳转到类、方法的界说处查看其原生注释是学习 Java编程、阅读项目源码非常有用的方法。
对象工厂子包
reflection包下的factory子包是一个对象工厂子包,该包中的类用来基于反射生产出各种对象。
我们首先看 ObjectFactory接口,它有以下几个方法。
· void setProperties(Properties):设置工厂的属性。
· <T> T create(Class<T>):传入一个类型,采用无参构造方法天生这个类型的实例。
· <T> T create(Class<T>,List<Class<?>>,List<Object>):传入一个目标类型、一个参数类型列表、一个参数值列表,根据参数列表找到相应的含参构造方法天生这个类型的实例。
· <T> boolean isCollection(Class<T>):判断传入的类型是否是集合类。
而 DefaultObjectFactory 继承了 ObjectFactory 接口,是默认的对象工厂实现。作为工厂,DefaultObjectFactory的 create方法用来生产对象,而两个 create方法最终都用到了代码6-12所示的 instantiateClass方法。
instantiateClass 方法能够通过反射找到与参数匹配的构造方法,然后基于反射调用该构造方法天生一个对象。
【代码6-12】
https://p3.pstatp.com/large/pgc-image/c5f1e0de3fe7408a90d4da6712bea236
https://p3.pstatp.com/large/pgc-image/eed5f5f196c44e6cba708ef42878aa8f
别的,DefaultObjectFactory中另有一个 resolveInterface方法。当传入的目标类型是一个接口时,该方法可以给出一个符合该接口的实现。例如,当要创建一个 Map对象时,最终会创建一个 HashMap对象。由于整个代码比较简单,不再附上。
执行器子包
reflection 包下的 invoker 子包是执行器子包,该子包中的类能够基于反射实现对象方法的调用和对象属性的读写。
学习了反射的基本概念之后,我们知道通过反射可以很方便地调用对象的方法和读写方法的属性。而 invoker子包则进一步封装和简化了这些操作。
invoker 子包有一个 Invoker 接口和三个实现,Invoker 接口及其实现类类图如图6-6所示。
https://p3.pstatp.com/large/pgc-image/42d6b5b86998473ebefaeeb6f4ff74c3图6-6 Invoker接口及其实现类类图
Invoker接口的三个实现分别用来处理三种差别情况。
· GetFieldInvoker:负责对象属性的读操作;
· SetFieldInvoker:负责对象属性的写操作;
· MethodInvoker:负责对象其他方法的操作。
我们先阅读 Invoker接口的源码,它只界说了以下两个抽象方法。
· invoke方法,即执行方法。该方法负责完成对象方法的调用和对象属性的读写。在三个实现类中,分别是属性读取操作、属性赋值操作、方法触发操作。
· getType方法,用来获取类型。它对于 GetFieldInvoker和 SetFieldInvoker的含义也是明确的,即得到目标属性的类型。可 MethodInvoker对应的是一个方法,getType方法对于 MethodInvoker类型而言的意义是什么呢?
阅读 MethodInvoker中的 getType方法,我们发现该方法直接返回了 MethodInvoker中的 type属性。该 type属性的界说如代码6-13所示。
【代码6-13】
https://p1.pstatp.com/large/pgc-image/03c511399e7b486c90564e123135243f
阅读代码6-13后可以得出结论,如果一个方法有且只有一个输入参数,则 type为输入参数的类型;否则,type为方法返回值的类型。
至此,关于 Invoker接口中 getType方法在三个实现类中的含义也就清晰了。
阅读源码时,重点关注自己理解不够清晰的点是让自己快速理解源码的一个小本事。
invoke方法的实现也非常简单,下面给出GetFieldInvoker中该方法的源码,如代码6-14所示。
【代码6-14】
https://p1.pstatp.com/large/pgc-image/9601c95bba1c44d6ac409ac638a33328
https://p1.pstatp.com/large/pgc-image/11f48ecc6f8d4cb4acf10faed231fe65
这样,我们对于 invoker子包中的源码已经有了全面的了解。
属性子包
reflection包下的 property子包是属性子包,该子包中的类用来完成与对象属性相干的操作。
如代码6-15所示,如果想让 user2的属性和 user1的属性完全划一,则必要对属性一一进行复制,这样的过程是繁杂的。
【代码6-15】
https://p1.pstatp.com/large/pgc-image/7076f80b0ac9457f988beff181f71223
PropertyCopier 作为属性复制器,就是用来解决上述问题的。借助于属性复制器PropertyCopier,我们可以方便地将一个对象的属性复制到另一个对象中,如代码6-16所示。
【代码6-16】
https://p9.pstatp.com/large/pgc-image/ad6f096e6755407a805fece2df0cde28
属性复制器 PropertyCopier的属性复制工作在 copyBeanProperties方法中完成,该方法的源码如代码6-17所示。
【代码6-17】
https://p3.pstatp.com/large/pgc-image/ad99190b66b746c3bb129937831dff96
https://p3.pstatp.com/large/pgc-image/ad1c18a18f4d4666a3fa08636d321bc4
copyBeanProperties方法的工作原理非常简单:通过反射获取类的所有属性,然后依次将这些属性值从源对象复制出来并赋给目标对象。
但是要注意一点,该属性复制器无法完成继承得来的属性的复制,由于getDeclaredFields方法返回的属性中不包含继承属性。
PropertyNamer提供属性名称相干的操作功能,例如,通过 get、set方法的方法名找出对应的属性等。要想让 PropertyNamer 正常地发挥作用,需包管对象属性、方法的定名遵循 Java Bean的定名规范,即:
· 如果类的成员变量的名字是 abc,那么该属性对应的读写方法分别定名为 getAbc()和 setAbc()。
· 如果类的属性是 boolean 类型,则允许使用“is”代替上面的“get”,读方法定名为 isAbc()。
了解了这个规范后,PropertyNamer中的相干代码也就非常简单了。
PropertyTokenizer 是一个属性标志器。传入一个形如“student.name”的字符串后,该标志器会将其拆分开,放入各个属性中。
拆分结束后各个属性的值如代码6-18中的注释所示。
【代码6-18】
https://p3.pstatp.com/large/pgc-image/b2ef34982a4a4ab2accf88a275f290b7
其中的操作全为字符串操作,留给读者自行分析。
对象包装器子包
reflection包下的 wrapper子包是对象包装器子包,该子包中的类使用装饰器模式对各种类型的对象(包括基本 Bean对象、集合对象、Map对象)进行进一步的封装,为其增长一些功能,使它们更易于使用。
wrapper子包类图如图6-7所示。
https://p1.pstatp.com/large/pgc-image/23d9c0dd21504c7b83bda0fb971e04c6图6-7 wrapper子包类图
ObjectWrapperFactory 是对象包装器工厂的接口,DefaultObjectWrapperFactory 是它的默认实现。不外该默认实现中并没有实现任何功能。MyBatis 也允许用户通过设置文件中的 objectWrapperFactory节点来注入新的 ObjectWrapperFactory。
ObjectWrapper接口是所有对象包装器的总接口。
以 BeanWrapper为例,我们介绍一下包装器的实现。在介绍之前我们先了解 reflection包中的两个类:MetaObject类和 MetaClass类。
meta 在中文中常译为“元”,在英文单词中作为词头有“涵盖”“超越”“变换”等多种含义。在这里,这三种含义都是存在的。例如,MetaObject类中涵盖了对应 Object类中的全部信息,并经过变化和拆解得到了一些更为细节的信息。因此,可以将 MetaObject类理解为一个涵盖对象(Object)中更多细节信息和功能的类,称为“元对象”。同理,MetaClass就是一个涵盖了类型(Class)中更多细节信息和功能的类,称为“元类”。
BeanWrapper有三个属性,其中的 metaObject属性从其父类 BaseWrapper继承而来。这三个属性的含义如代码6-19中的注释所示。
【代码6-19】
https://p9.pstatp.com/large/pgc-image/5e3df8a46b6f4faeb9222fcc24c50943
通过对 BeanWrapper属性的了解,加上对 MetaObject类和 MetaClass类的简单介绍,可以得出结论: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的父类,为这两个类提供一些共用的基础方法。
源码阅读时,遇到同类型的类(一般具有类似的名称、功能),可以重点阅读其中的一个类。当这个类的源码阅读清晰时,同类型类的源码也就清晰了。
反射核心类
reflection包中最为核心的类就是 Reflector类。图6-8给出了与Reflector类最为密切的几个类的类图。
https://p1.pstatp.com/large/pgc-image/c415395b0284424d8ceaaed68d4c69a4图6-8 Reflector类及相干类的类图
Reflector 类负责对一个类进行反射剖析,并将剖析后的结果在属性中存储起来。该类包含的属性如代码6-20所示,各个属性的含义已经通过注释进行了标注。
【代码6-20】
https://p3.pstatp.com/large/pgc-image/aa6dc0288a6e4d78b1a990295631cb09
https://p1.pstatp.com/large/pgc-image/f9eefbe6b7104934b8da94b296838915
Reflector 类将一个类反射剖析后,会将该类的属性、方法等一一归类放到以上的各个属性中。因此 Reflector类完成了主要的反射剖析工作,这也是我们将其称为反射核心类的原因。reflection包中的其他类则多是在其反射结果的基础上进一步包装的,使整个反射功能更易用。Reflector类反射剖析一个类的过程是由构造函数触发的,逻辑非常清晰。Reflector类的构造函数如代码6-21所示。
【代码6-21】
https://p1.pstatp.com/large/pgc-image/5585fd48d9974022a92d60bc992bb97d
https://p1.pstatp.com/large/pgc-image/8409194f7be049f99fbd2643cd79e80c
具体到每个子方法,其逻辑比较简单。下面以其中的 addGetMethods 方法为例进行介绍。addGetMethods方法的功能是分析参数中传入的类,将类中的 get方法添加到 getMethods方法中。其带注释的源码如代码6-22所示。
【代码6-22】
https://p1.pstatp.com/large/pgc-image/460b84ad504145278c0d22b525eca9df
其中的 conflictingGetters变量是一个 Map,它的 key是属性名称,value是该属性可能的get方法的列表。但是,最终每个属性真正的get方法应该只有一个。resolveGetterConflicts方法负责尝试找出该属性真正的 get方法,该方法源码并不复杂,读者可以自行阅读。
ReflectorFactory是 Reflector的工厂接口,而 DefaultReflectorFactory是该工厂接口的默认实现。下面直接以 DefaultReflectorFactory为例,介绍 Reflector工厂。DefaultReflectorFactory 中最核心的方法就是用来天生一个类的Reflector 对象的findForClass方法,如代码6-23所示。
【代码6-23】
https://p1.pstatp.com/large/pgc-image/c75bde4445684b2a832fe9ff91d5dd58
https://p3.pstatp.com/large/pgc-image/78aaa8c1484740fcb43bd21df99b4519
反射包装类
reflection包中存在许多的包装类,它们使用装饰器模式(又称包装模式)将许多反射相干的类包装得更为易用。前面介绍过的 wrapper子包中就存在大量的这种包装类,并且我们在 6.5节提到,wrapper子包中的包装类依赖两个更为基础的包装类:MetaClass类和MetaObject类。
MetaObject 被称为元对象,是一个针对普通 Object 对象的反射包装类,其属性如代码6-24所示。
【代码6-24】
https://p1.pstatp.com/large/pgc-image/92eeeceb0c8f4bd4b74be4cd8e8768ee
整个包装类中除了原始对象本身外,还包装了对象包装器、对象工厂、对象包装器工厂、反射工厂等。因此,只要使用 MetaObject对一个对象进行包装,包装类中就具有大量的辅助类,便于进行各种反射操作。
SystemMetaObject中限定了一些默认值,其中的 forObject方法可以使用默认值输出一个 MetaObject对象。因此,SystemMetaObject是一个只能使用默认值的 MetaObject工厂。MetaClass 被称为元类,它是针对类的进一步封装,其内部集成了类可能使用的反射器和反射器工厂。
异常拆包工具
ExceptionUtil 是一个异常工具类,它提供一个拆包异常的工具方法 unwrapThrowable。该方法将 InvocationTargetException 和 UndeclaredThrowableException 这两类异常进行拆包,得到其中包含的真正的异常。
unwrapThrowable方法带注释的源码如代码6-25所示。
【代码6-25】
https://p1.pstatp.com/large/pgc-image/dbd58334ddaf438bb08518e1e8b16b46
https://p3.pstatp.com/large/pgc-image/651930eea4fa4c4abb62455372efc1d0
unwrapThrowable 方法的结构非常简单,但是我们必要思考它存在的意义:为什么必要给 InvocationTargetException和 UndeclaredThrowableException这两个类拆包?这两个类为什么要把其他异常包装起来?
许多时候读懂源码的实现并不难,但是一定要多思考源码为什么这么写。只有这样,才气在源码阅读的过程中有更多的劳绩。
接下来通过了解 InvocationTargetException和 UndeclaredThrowableException这两个类来解答上述疑问。InvocationTargetException为必检异常,UndeclaredThrowableException为免检的运行时异常。它们都不属于 MyBatis,而是来自 java.lang.reflect包。
反射操作中,署理类通过反射调用目标类的方法时,目标类的方法可能抛出异常。反射可以调用各种目标方法,因此目标方法抛出的异常是多种多样无法确定的。这意味着反射操作可能抛出一个任意类型的异常。可以用 Throwable 去吸收这个异常,但这无疑太过宽泛。
InvocationTargetException就是为解决这个问题而设计的,当反射操作的目标方法中出现异常时,都统一包装成一个必检异常 InvocationTargetException。InvocationTargetException内部的 target 属性则保存了原始的异常。这样一来,便使得反射操作中的异常更易管理。InvocationTargetException类带注释的源码如代码6-26所示。
【代码6-26】
https://p9.pstatp.com/large/pgc-image/4cf69e303dfc4c60aeaa8c112c2fc4db
https://p1.pstatp.com/large/pgc-image/5c21402fd6e648d5837d236dca70aa7d
讲完了 InvocationTargetException,下面再讲讲 UndeclaredThrowableException。
根据 Java的继承原则,我们知道:如果子类中要重写父类中的方法,那么子类方法中抛出的必检异常必须是父类方法中声明过的类型。
在建立目标类的署理类时,通常是建立了目标类接口的子类或者目标类的子类(10.1.3节和 22.1.1节会详细介绍动态署理)。因此,将Java的继承原则放在署理类和被署理类上可以演化为:
· 如果署理类和被署理类实现了共同的接口,则署理类方法中抛出的必检异常必须是在共同接口中声明过的;
· 如果署理类是被署理类的子类,则署理类方法中抛出的必检异常必须是在被署理类的方法中声明过的。
但是在署理类中难免会在执行某些方法时抛出一些共同接口或者父类方法中没有声明的必检异常,那这个问题该怎么解决呢?如果不抛出,则它是必检异常,必须抛出;如果抛出,则父接口或父类中没有声明该必检异常,不能抛出。
答案就是这些必检异常会被包装为免检异常 UndeclaredThrowableException 后抛出。以是说 UndeclaredThrowableException 也是一个包装了其他异常的异常,UndeclaredThrowableException 类带注释的源码如代码6-27 所示,其包装的异常在undeclaredThrowable属性中。
【代码6-27】
https://p9.pstatp.com/large/pgc-image/f6910db2c4bb45a7bb2f1c6b4567bcd1
有一个简单的例子可以恰好同时涉及 InvocationTargetException和 Undeclared-hrowablexception 这两个异常。就是署理类在进行反射操作时发生异常,于是异常被包装成 InvocationTargetException。
InvocationTargetException显然没有在共同接口或者父类方法中声明过,于是又被包装成了UndeclaredThrowableException。这样,真正的异常就被包装了两层。这也是为什么在ExceptionUtil的unwrapThrowable方法中存在一个“while (true)”死循环,用来连续拆包。
总之,InvocationTargetException 和 UndeclaredThrowableException 这两个类都是异常包装类,必要拆包后才气得到真正的异常类。
而 ExceptionUtil的 unwrapThrowable方法就可以完成该拆包工作。
参数名剖析器
ParamNameResolver 是一个参数名剖析器,用来按顺序列出方法中的虚参,并对实参进行名称标注。参数名剖析器中主要涉及的是字符串的处理,因此关于参数名剖析器的源码阅读我们也采用一种比较特殊的方法:断点调试法。
ParamNameResolver类的属性如代码6-28所示。
【代码6-28】
https://p1.pstatp.com/large/pgc-image/47d1e07c8127499ebbfb0869aa50bb00
ParamNameResolver 类主要的方法有两个:构造方法 ParamNameResolver 和getNamedParams方法。
构造方法 ParamNameResolver能够将目标方法的参数名称依次列举出来。在列举的过程中,如果某个参数存在@Param注解,则会用注解的value值更换参数名。
我们直接使用断点调试法探究该方法的功能。假设有代码6-29所示的方法。
【代码6-29】
https://p3.pstatp.com/large/pgc-image/5f28da2980544354935bfcce11df05d8
经过构造方法 ParamNameResolver 剖析后,可以通过调试工具看到属性 names 和hasParamAnnotation中的值,如图6-9所示。
https://p3.pstatp.com/large/pgc-image/6fd3d849b2b04fd9a6b026f466249eae图6-9 参数名剖析器运行结果(1)
而 getNamedParams方法是在构造方法确定的 names属性和 hasParamAnnotation属性值的基础上,给出实参的参数名。例如,使用代码6-30所示的语句调用代码6-29中的方法。
【代码6-30】
https://p3.pstatp.com/large/pgc-image/fd829b154eac4d7891bdbc323a8e21ef
调用后,则 getNamedParams会给出如图6-10所示的输出。
https://p3.pstatp.com/large/pgc-image/c44134dc9b8b4957b55669186f5fdeec图6-10 参数名剖析器运行结果(2)
这样,我们就在映射文件中使用变量名“param3”或者“emailAddress”引用了参数值“[email protected]”。
有了断点调试的结果后,再阅读构造方法 ParamNameResolver和 getNamedParams方法就变得非常简单,留给大家自行完成。
断点调试法在阅读字符串处理类的函数时十分有用,由于打断点的方式能够将字符串处理过程中的所有中心值显现出来,便于把握步伐的每一步流程。
泛型剖析器
在上一节中使用断点调试法进行了源码阅读,在这一节中继续使用这个方法。我们会以某个测试用例为依据,随着该测试用例的逻辑跳转完成整个类的源码阅读。
TypeParameterResolver是泛型参数剖析器。在阅读它的源码之前我们先弄清一个问题:它的功能是什么?许多情况下,弄清一个类的功能对阅读其源码十分必要。
假设有 User和 Student两个类,分别如代码6-31和代码6-32所示。
【代码6-31】
https://p3.pstatp.com/large/pgc-image/0e00ff4f8ea04f0cb35ef44cd26559b2
【代码6-32】
https://p3.pstatp.com/large/pgc-image/607018eaaf1a47ebb86ea036b593c9e1
请问:Student类中的 getInfo方法(继承自父类 User)的输出参数类型是什么?
答案很简单,是“List<Number>”。但是得出这个答案的过程却涉及 User 和 Student两个类。首先通过 User 类确定 getInfo 方法的输出结果是“List<T>”,然后通过 Student类得知“T”被设置为“Number”。因此,Student 类中的 getInfo 方法的输出参数是“List<Number>”。
TypeParameterResolver 类的功能就是完成上述分析过程,资助 MyBatis 推断出属性、返回值、输入参数中泛型的具体类型。例如,通过代码6-33 所示的调用,TypeParameterResolver便分析出 User类中的 getInfo方法的输出参数是“List<Object>”,Student类中的 getInfo方法的输出参数是“List<Number>”。
【代码6-33】
https://p1.pstatp.com/large/pgc-image/6bd9e0cd00df4bc68e9108469e4b3da2
泛型剖析器运行结果如图6-11所示。
https://p1.pstatp.com/large/pgc-image/7696c637d94249c496694f967bac3540
https://p3.pstatp.com/large/pgc-image/49afb4b6ad544df1b456dc3a695fa3eb图6-11 泛型剖析器运行结果
了解了 TypeParameterResolver类的功能后,下面来查看它的源码。它对外提供以下三个方法。
· resolveFieldType:剖析属性的泛型;
· resolveReturnType:剖析方法返回值的泛型;
· resolveParamTypes:剖析方法输入参数的泛型。
上述这三个方法都只是将要剖析的变量从属性、方法返回值、方法输入参数中找出来。变量的泛型剖析擦?鲱核心的工作。以代码6-34所示的 resolveParamTypes方法为例,该方法将变量从方法输入参数中找出后,对每个变量都调用了 resolveType 方法。因此,resolveType是最重要的方法。
【代码6-34】
https://p1.pstatp.com/large/pgc-image/870d536b19f149a28644b33474e578d8
https://p3.pstatp.com/large/pgc-image/cf19299af03c4fa7a39afea522acab9e
resolveType方法根据目标类型的差别调用差别的子方法进行处理。
在分析 resolveType方法的源码之前,有必要再强调一下 resolveType的输入参数,以防大家混淆。以上文中提到的“Student类中的 getInfo方法(继承自父类 User)的输出参数类型是什么?”这一问题为例,则:
· type:指要分析的字段或者参数的类型。这里我们要分析的是getInfo的输出参数,即“List<T>”的类型。
· srcType:指要分析的字段或者参数所属的类。我们这里要分析的是 Student类中的getInfo方法,故所属的类是 Student类。
· declaringClass:指界说要分析的字段或者参数的类。getInfo方法在 User 类中被界说,故这里是 User类。
resolveType方法的带注释源码如代码6-35所示。
【代码6-35】
https://p3.pstatp.com/large/pgc-image/ac36dbe37c7d47b58db2f6d54e88f1a4
https://p3.pstatp.com/large/pgc-image/2b343095e1aa4a09b8514b6f0938636a
resolveType 根据差别的参数类型调用了差别的子方法进行处理。
我们直接以“List<T>”对应的 resolveParameterizedType子方法为例进行分析,而该子方法也是所有子方法中最为复杂的一个。
“List<T>”作为参数化类型会触发 resolveParameterizedType方法进行处理。resolveParameterizedType方法的带注释源码如代码6-36所示。
【代码6-36】
https://p1.pstatp.com/large/pgc-image/0ca799632eca48efa84275efa24f3e18
对于 resolveParameterizedType方法中的各种分支情况我们已经在代码6-36中通过注释进行了详细阐明。在示例中,parameterizedType 为“List<T>”,因此会继续调用resolveTypeVar方法对泛型变量“T”进行进一步的剖析。
resolveTypeVar方法的带注释源码如代码6-37所示。resolveTypeVar方法会尝试通过继承关系等确定泛型变量的具体结果。
【代码6-37】
https://p1.pstatp.com/large/pgc-image/7522b973e93d4144926e776bc57e0ce3
https://p3.pstatp.com/large/pgc-image/9da1f5ff68874df3bdc955142730dfde
这样,我们以“Student类中的 getInfo方法(继承自父类 User)的输出参数类型是什么?”这一问题为主线,对 TypeParameterResolver的源码进行了阅读。
在代码6-35 所示的 resolveType 方法中,会根据变量的类型调用resolveTypeVar、resolveParameterizedType、resolveGenericArrayType三个方法进行剖析。而在本节中,我们通过示例“List<T>”对 resolveTypeVar(代码6-37)、resolveParameterizedType(代码6-36)的源码进行了阅读。而 resolveGenericArrayType方法的带注释源码如代码6-38所示。
【代码6-38】
https://p1.pstatp.com/large/pgc-image/c7bbb119407d4387985afe8fab5c0041
https://p1.pstatp.com/large/pgc-image/72a74cdf53104d129b04ade5dcfd1f4f
resolveGenericArrayType方法并不复杂,只是根据元素类型又调用了其他几个方法。、
这样,我们以断点调试法为基础,以“List<T>”类型的泛型变量为用例,通过以点带面的方式完成了 TypeParameterResolver类的源码阅读。这种以用例为主线的源码阅读方法能资助我们排除许多干扰从而专注于一条逻辑主线。而等这条逻辑主线的源码被阅读清晰时,其他逻辑主线往往也会迎刃而解。
本文给大家讲解的内容是mybatis源码详解:reflection包源码阅读
[*]下篇文章给大家讲解的是mybatis源码详解:annotations包与lang包;
[*]觉得文章不错的朋友可以转发此文关注小编;
[*]感谢大家的支持
页:
[1]