[MyBatis源码分析 - 反射器模块]
一、背景介绍
MyBatis作为一个ORM框架,一个核心的功能是数据库数据和Java POJO(即Java Bean)之间的转换,MyBatis是基于JDBC封装的,先来看一段简单的JDBC代码。
public Role getRole(Long id) {
Connection connection = getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = connection.prepareStatement("select id, role_name, note from t_role where id = ?");
ps.setLong(1, id);
rs = ps.executeQuery();
while (rs.next()) {
Long roleId = rs.getLong("id");
String roleName = rs.getString("role_name");
String note = rs.getString("note");
Role role = new Role();
role.setId(id);
role.setRoleName(roleName);
role.setNote(note);
return role;
}
} catch (Exception e) {
Logger.getLogger(JdbcExample.class.getName()).log(Level.SEVERE, null, e);
} finally {
this.close(rs, ps, connection);
}
return null;
}
从上面代码可以看到JDBC执行一条带参数的SQL时,要有传入的参数值id
,还要知道参数值的类型Long
,这样才知道要调用PreparedStatement
对象的setLong
方法。MyBatis执行SQL时,一般传入的参数类型是一个Java Bean,设置SQL参数往往是用到Java Bean的部分成员属性,获取参数值通过属性的getter
方法获取,属性类型需要通过反射获取,一般简单使用的Java Bean的属性是跟数据库数据表的字段对应的,eg:
public class Role {
private long id;
private String roleName;
private String note;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
POJO对应的数据表结构
当然,在更加复杂的业务模型中,POJO和表结构不是简单的一对一关系,可能一个POJO组合在另一个POJO中,常见于表关联产生的一对一、一对多映射中。POJO成员属性的数据类型虽然常见是基本类型的包装类(不建议直接使用基本类型如
int
、long
,而是使用它们的包装类Integer
、Long
,因为基本类型会有默认值,而包装类型是对象默认为null),但也有可能是集合类型如List
(比如一对多一个订单下有多个商品),还可能是Map
(当然Map
更多用在传参而不是成员属性类型或者POJO类型这种语义不强的场景)。
如果业务模型注重建模,POJO可能还会有继承关系,继承意味子类也继承了父类的属性,还有更复杂的情况,为了灵活使用,一些父类中的属性类型可能会被设计成泛型,形式可能为<T>
(普通泛型)、<T extends Xxx>
、<T super Yyy>
(限制泛型)、<? extends Xxx>
、<? super Yyy>
(通配符泛型),这些都增加了POJO和数据表数据转换的难度,因为执行SQL设置参数、SQL执行结果映射转化为Java对象,都需要知道实际使用时属性的确切类型,还有调用相应属性的setter
、getter
,这些都需要依赖Java的反射API来实现。
Java反射API虽然功能强大,但编写出高质量的反射代码难度较高,比较复杂且容易出错,为了简化反射操作的相关代码,MyBatis提供了专门的反射模块,该模块对 Java 原生的反射进行了良好的封装,提了更加简洁易用的 API,方便上层使调用,并且对反射操作进行了一系列优化,比如将类的元信息封装缓存在
MetaClass
对象中,提高了反射操作的性能。
反射模块的代码一览如下:
二、功能概述
根据上面的背景介绍和需求分析总结,反射器模块需要实现的功能有:
- 1、获取类的元信息,包括:构造方法、可读/可写的所有属性、属性对应的
getter/setter
方法 - 2、根据类元信息创建Java对象,主要是在执行完SQL将数据转化为Java对象时用到
关于相关组件介绍的系列文章如下:
- [MyBatis源码分析 - 反射器模块 - 组件一] Reflector
- [MyBatis源码分析 - 反射器模块 - 组件二] Invoker
- [MyBatis源码分析 - 反射器模块 - 组件三] ObjectFactory
- [[MyBatis源码分析 - 反射器模块 - 组件四] TypeParameterResolver]
- [MyBatis源码分析 - 反射器模块 - 组件五] Property 工具集
- [MyBatis源码分析 - 反射器模块 - 组件六] MetaClass
- [MyBatis源码分析 - 反射器模块 - 组件七] ObjectWrapper
- [MyBatis源码分析 - 反射器模块 - 组件八] MetaObject
三、Reflector
Reflector
是整个反射器模块的基础,通过其解析类的元信息并缓存起来,源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件一] Reflector。
四、Invoker
Invoker
是一个接口,缓存了类属性对应的 Field 对象和 getter/setter 方法对应的 Method 对象,设置获取属性值、调用方法,实际上是调用 Field 对象和 Method 对象的反射方法去完成,Reflector
中的 getMethods
、setMethods
属性缓存了属性和对应的 Invoker 对象的映射,源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件二] Invoker。
五、ObjectFactory
ObjectFactory
从名字上看很容器猜出用途,对象工厂就是用来创建对象的,MyBatis 中会使用该类来创建所有需要的新对象,源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件三] ObjectFactory。
六、TypeParameterResolver
TypeParameterResolver
是一个类型参数解析器,即JavaBean中可能带泛型如 List<T>、通配符如 <? super SubClass>,而且可能复杂的继承关系,为了获取到运行时正确具体的类型(包括属性类型、方法参数类型、方法返回值类型),需要使用该类解析类型参数,源码解析详见:[[MyBatis源码分析 - 反射器模块 - 组件四] TypeParameterResolver]。
七、Property 工具集
主要有三个工具类:PropertyCopier
、PropertyNamer
、PropertyTokenizer
。分别用来拷贝对象属性值、从方法名提取属性信息、解析属性表达式,源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件五] Property 工具集。
八、MetaClass
MetaClass
通过对 Reflector
和 PropertyTokenizer
组合使用,实现了对复杂的属性表达式的解析,并实现了获取指定属性描述信息的功能,源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件六] MetaClass。
九、ObjectWrapper
ObjectWrapper
接口是对对象的包装,抽象了对象的属性信息,它定义了一系列查询对象属性信息、以及更新属性的方法,源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件七] ObjectWrapper。
十、MetaObject
MetaObject
封装了 ObjectWrapper 对象和相应配套的工厂对象,主要联合实现对表达式的解析和配套的操作,形成一组完成的操作对象的方法,其源码解析详见:[MyBatis源码分析 - 反射器模块 - 组件八] MetaObject。