使用示例
在处理SQL的参数映射以及结果集的映射时,Mybatis使用了大量的MetaObject
对象,该对象也是Mybatis中设计得比较精妙的一个组件,主要用来处理JavaBean与参数以及结果集之间的相互映射,其核心逻辑即为反射。
在开始研究前,先来两个简单示例,感受一下MetaObject
的强大之处。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public class MetaObjectTest {
@Data
static class User {
private String name;
private UserGroup userGroup;
private List<Tag> tags = new ArrayList<>();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class UserGroup {
private String name;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Tag {
private String name;
}
@Test
public void test1() {
final User user = new User();
// 使用MetaObject装饰对象
final MetaObject metaObject = new Configuration().newMetaObject(user);
metaObject.setValue("name", "张三");
metaObject.setValue("userGroup.name", "管理组");
// MetaObjectTest.User(name=张三, userGroup=MetaObjectTest.UserGroup(name=管理组), tagSet=[])
System.out.println(user);
}
@Test
public void test2() {
final User user = new User();
user.setName("李四");
user.setUserGroup(new UserGroup("客服组"));
final List<Tag> tags = new ArrayList<>();
for (int i = 0; i < 3; i++)
tags.add(new Tag("Tag-" + i));
user.setTags(tags);
final MetaObject metaObject = new Configuration().newMetaObject(user);
// 李四
System.out.println(metaObject.getValue("name"));
// 客服组
System.out.println(metaObject.getValue("userGroup.name"));
// Tag-0
System.out.println(metaObject.getValue("tags[0].name"));
}
}
|
如单元测试所示,我们可以通过MetaObject
对象装饰普通的JavaBean
,来实现强大的类似于JSONPATH
的功能,由于Mybatis
是一个强大的ORM
框架,势必会涉及到大量的反射处理,该工具类的出现,大大的简化了Mybatis
框架内部操作属性的复杂度。
MetaObject
相继依赖了BeanWrapper
、MetaClass
、Reflector
,他们之间的关系如下图所示

整体的依赖结构大致如以下伪代码:
1
2
3
4
5
6
7
8
9
10
11
|
class MetaObject {
ObjectWrapper objectWrapper; // 实现类有BaseWrapper,BeanWrapper,MapWrapper, CollectionWrapper
}
class BeanWrapper {
MetaClass metaClass;
}
class MetaClass {
Reflector reflector;
}
|
- BeanWrapper:与
MetaObject
类似,不同点是BeanWrapper
只针对当前对象的属性进行操作,不支持子属性。
- MetaClass:类的反射功能支持,能获取完整类的属性,包括子属性类的属性。
- Refector:类的反射功能支持,只针对当前类的属性,不支持子属性。
为了便于分析完整的获取属性流程,咱们再来写一个稍微复杂一点的数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class MetaObjectTest2 {
@Test
public void test() {
final User user = buildUser();
final MetaObject metaObject = new Configuration().newMetaObject(user);
// 查找user对象中第一个tag的创建者姓名
final Object value = metaObject.getValue("tags[0].creator.name");
System.out.println(value);
}
@Data
static class User {
private String name;
private List<Tag> tags = new ArrayList<>();
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Tag {
private String name;
private User creator;
}
static User buildUser() {
final User user = new User();
user.setName("张三");
final List<Tag> tags = new ArrayList<>();
for (int i = 0; i < 3; i++) {
final User creator = new User();
creator.setName("Creator-" + i);
tags.add(new Tag("Tag-" + i, creator));
}
user.setTags(tags);
return user;
}
}
|

首先,将传入的表达式经过属性分词器(PropertyTokenizer
)进行分解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
// 示例:tags[0].creator.name
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
// 当前属性名: tags
private String name;
// 索引名:tags[0]
private final String indexedName;
// 索引(如果有):0
private String index;
// 子属性:creator.name
private final String children;
public PropertyTokenizer(String fullname) {
int delim = fullname.indexOf('.');
if (delim > -1) {
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
name = fullname;
children = null;
}
indexedName = name;
delim = name.indexOf('[');
if (delim > -1) {
index = name.substring(delim + 1, name.length() - 1);
name = name.substring(0, delim);
}
}
@Override
public boolean hasNext() {
// 判断是否有子属性
return children != null;
}
@Override
public PropertyTokenizer next() {
// 继续解析子属性,再获得一个PropertyTokenizer对象
return new PropertyTokenizer(children);
}
}
|
再通过PropertyTokenizer.hasNext()
判断是否有子属性,如果有,则调用metaObjectForProperty()
方法继续处理子属性,反之则直接使用依赖的objectWrapper
(这里用的是BeanWrapper
)进行获取。

在metaObjectForProperty()
方法中,将子属性继续转换为MetaObject
对象,并继续调用metaObject.getValue()
方法

最终。所有的子属性递归(类似)完成后,调用objectWrapper.get()
来实现当前类底层的属性获取。那么接着往下看


通过BeanWrapper
对象中依赖的MetaClass
对象,获取到对应的MethodInvoker
对象,最终实现getName()
的反射调用。

该示例涉及到多级嵌套,故而会执行多次MethodInvoker.invoke()
,分别是user.getTags()
-> tag.getCreator()
-> creator.getName()
;
最后,来个简单的流程图总结一下吧

评论