博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis 源码分析——动态代理----动态获取Mapper接口实例
阅读量:2289 次
发布时间:2019-05-09

本文共 7016 字,大约阅读时间需要 23 分钟。

MyBatis框架是如何去执行SQL语句?相信不只是你们,笔者也想要知道是如何进行的。相信有上一章的引导大家都知道SqlSession接口的作用。当然默认情况下还是使用DefaultSqlSession类。关于SqlSession接口的用法有很多种。笔者还是比较喜欢用getMapper方法。对于getMapper方法的实现方式。笔者不能下一个定论。笔者只是想表示一下自己的理解而以——动态代理。
笔者把关于getMapper方法的实现方式理解为动态代理。事实上笔者还想说他可以是一个AOP思想的实现。那么具体是一个什么样子东西。相信笔者说了也不能代表什么。一切还是有大家自己去查看和理解。从源码上我们可以看到getMapper方法会去调用Configuration类的getMapper方法。好了。一切的开始都在这里了。
DefaultSqlSession类:
public
<T> T getMapper(Class<T> type) {
return
configuration.<T>getMapper(type,
this
); }
对于Configuration类上一章里面就说明他里面存放了所有关于XML文件的配置信息。从参数上我们可以看到他要我们传入一个Class<T>类型。这已经可以看到后面一定要用到反射机制和动态生成相应的类实例。让我们进一步查看一下源码。
Configuration类:
public
<T> T getMapper(Class<T> type, SqlSession sqlSession) {
return
mapperRegistry.getMapper(type, sqlSession); }
当笔者点击进来发现他又调用MapperRegistry类的getMapper方法的时候,心里面有一种又恨又爱的冲动——这就是构架之美和复杂之恨。MapperRegistry类笔者把他理解存放动态代理工厂(MapperProxyFactory类)的库存。当然我们还是进去看一看源码吧。
MapperRegistry类:
1
public
<T> T getMapper(Class<T> type, SqlSession sqlSession) {
2
final
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
3
if
(mapperProxyFactory ==
null
) {
4
throw
new
BindingException("Type " + type + " is not known to the MapperRegistry.");
5
}
6
try
{
7
return
mapperProxyFactory.newInstance(sqlSession);
8
}
catch
(Exception e) {
9
throw
new
BindingException("Error getting mapper instance. Cause: " + e, e);
10
}
11
}
好了。笔者相信大家看到这一段代码的时候都明白——MapperRegistry类就是用来存放MapperProxyFactory类的。我们还是在看一下knownMappers成员是一个什么要样子的集合类型。
private
final
Map<Class<?>, MapperProxyFactory<?>> knownMappers =
new
HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers是一个字典类型。从Key的类型上我们可以判断出来是一个类一个动态代理工厂。笔者看到这里的时候都会去点击一个MapperProxyFactory类的源码。看看他里面又是一些什么东东。
1
public
class
MapperProxyFactory<T> {
2
3
private
final
Class<T> mapperInterface;
4
private
final
Map<Method, MapperMethod> methodCache =
new
ConcurrentHashMap<Method, MapperMethod>();
5
6
public
MapperProxyFactory(Class<T> mapperInterface) {
7
this
.mapperInterface = mapperInterface;
8
}
9
10
public
Class<T> getMapperInterface() {
11
return
mapperInterface;
12
}
13
14
public
Map<Method, MapperMethod> getMethodCache() {
15
return
methodCache;
16
}
17
18
@SuppressWarnings("unchecked")
19
protected
T newInstance(MapperProxy<T> mapperProxy) {
20
return
(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new
Class[] { mapperInterface }, mapperProxy);
21
}
22
23
public
T newInstance(SqlSession sqlSession) {
24
final
MapperProxy<T> mapperProxy =
new
MapperProxy<T>(sqlSession, mapperInterface, methodCache);
25
return
newInstance(mapperProxy);
26
}
27
28
}
还好。代码不是很多,理解起来也不是很复杂。略看一下源码,笔者做了一个很大胆的猜测——一个类,一个动态代理工厂,多个方法代理。我们先把猜测放在这里,然后让我们回到上面部分吧。我们发现MapperRegistry类的getMapper方法里面最后会去调用MapperProxyFactory类的newInstance方法。这个时候我们又看到他实例化了一个MapperProxy类。MapperProxy类是什么。这个就关系到Proxy类的用法了。所以读者们自己去查看相关资料了。意思明显每执行一次XxxMapper(例如:笔者例子里面的IProductMapper接口)的方法都会创建一个MapperProxy类。方法执行之前都会先去调用相应MapperProxy类里面的invoke方法。如下
MapperProxy类:
1
public
Object invoke(Object proxy, Method method, Object[] args)
throws
Throwable {
2
if
(Object.
class
.equals(method.getDeclaringClass())) {
3
try
{
4
return
method.invoke(
this
, args);
5
}
catch
(Throwable t) {
6
throw
ExceptionUtil.unwrapThrowable(t);
7
}
8
}
9
final
MapperMethod mapperMethod = cachedMapperMethod(method);
10
return
mapperMethod.execute(sqlSession, args);
11
}
从源码的意思:从缓存中获得执行方法对应的MapperMethod类实例。如果MapperMethod类实例不存在的情况,创建加入缓存并返回相关的实例。最后调用MapperMethod类的execute方法。
到这里笔者小结一下,上面讲到笔者例子里面用到的getMapper方法。getMapper方法就是用来获得相关的数据操作类接口。而事实数据操作类邦定了动态代理。所以操据操作类执行方法的时候,会触动每个方法相应的MapperProxy类的invoke方法。所以invoke方法返回的结果就是操据操作类执行方法的结果。这样子我们就知道最后的任务交给了MapperMethod类实例。
MapperMethod类里面有俩个成员:SqlCommand类和MethodSignature类。从名字上我们大概的能想到一个可能跟SQL语句有关系,一个可能跟要执行的方法有关系。事实也是如此。笔者查看了SqlCommand类的源码。确切来讲这一部分的内容跟XxxMapper的XML配置文件里面的select节点、delete节点等有关。我们都会知道节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。要找到MappedStatement类就必须通过id来获得。有一个细节要注意:代码用到的id = 当前接口类 + XML文件的节点的ID属性。如下
1
public
SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
2
String statementName = mapperInterface.getName() + "." + method.getName();
3
MappedStatement ms =
null
;
4
if
(configuration.hasStatement(statementName)) {
5
ms = configuration.getMappedStatement(statementName);
6
}
else
if
(!mapperInterface.equals(method.getDeclaringClass())) {
// issue #35
7
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
8
if
(configuration.hasStatement(parentStatementName)) {
9
ms = configuration.getMappedStatement(parentStatementName);
10
}
11
}
12
if
(ms ==
null
) {
13
if
(method.getAnnotation(Flush.
class
) !=
null
){
14
name =
null
;
15
type = SqlCommandType.FLUSH;
16
}
else
{
17
throw
new
BindingException("Invalid bound statement (not found): " + statementName);
18
}
19
}
else
{
20
name = ms.getId();
21
type = ms.getSqlCommandType();
22
if
(type == SqlCommandType.UNKNOWN) {
23
throw
new
BindingException("Unknown execution method for: " + name);
24
}
25
}
26
}
看到这里的时候,我们就可以回头去找一找在什么时候增加了MappedStatement类。上面之所以可以执行是建立在XML配置信息都被加载进来了。所以MappedStatement类也一定是在加载配置的时候就进行的。请读者们自行查看一下MapperBuilderAssistant类的addMappedStatement方法——加深理解。SqlCommand类的name成员和type成员我们还是关注一下。name成员就是节点的ID,type成员表示查寻还是更新或是删除。至于MethodSignature类呢。他用于说明方法的一些信息,主要有返回信息。
笔者上面讲了这多一点主要是为了查看execute方法源码容易一点。因为execute方法都要用到SqlCommand类和MethodSignature类。
1
public
Object execute(SqlSession sqlSession, Object[] args) {
2
Object result;
3
switch
(command.getType()) {
4
case
INSERT: {
5
Object param = method.convertArgsToSqlCommandParam(args);
6
result = rowCountResult(
sqlSession.insert(command.getName(), param)
);
7
break
;
8
}
9
case
UPDATE: {
10
Object param = method.convertArgsToSqlCommandParam(args);
11
result = rowCountResult(
sqlSession.update(command.getName(), param)
);
12
break
;
13
}
14
case
DELETE: {
15
Object param = method.convertArgsToSqlCommandParam(args);
16
result = rowCountResult(
sqlSession.delete(command.getName(), param)
);
17
break
;
18
}
19
case
SELECT:
20
if
(method.returnsVoid() && method.hasResultHandler()) {
21
executeWithResultHandler(sqlSession, args);
22
result =
null
;
23
}
else
if
(method.returnsMany()) {
24
result = executeForMany(sqlSession, args);
25
}
else
if
(method.returnsMap()) {
26
result = executeForMap(sqlSession, args);
27
}
else
if
(method.returnsCursor()) {
28
result = executeForCursor(sqlSession, args);
29
}
else
{
30
Object param = method.convertArgsToSqlCommandParam(args);
31
result = s
qlSession.selectOne(command.getName(), param)
;
32
}
33
break
;
34
case
FLUSH:
35
result = sqlSession.flushStatements();
36
break
;
37
default
:
38
throw
new
BindingException("Unknown execution method for: " + command.getName());
39
}
40
if
(result ==
null
&& method.getReturnType().isPrimitive() && !method.returnsVoid()) {
41
throw
new
BindingException("Mapper method '" + command.getName()
42
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
43
}
44
return
result;
45
}
重点部分就是这里,我们会发现我们转了一圈,最后还是要回到SqlSession接口实例上。完美的一圈!笔者用红色标出来了。
看到了这里我们就清楚调头去看一下SqlSession接口实例吧。

转载地址:http://tjbnb.baihongyu.com/

你可能感兴趣的文章
Basic Calculator
查看>>
Count Complete Tree Nodes
查看>>
Contains Duplicate III
查看>>
c语言const 转为非const的问题
查看>>
Combination Sum III
查看>>
sublime2 ubuntu 安装
查看>>
Kth Largest Element in an Array
查看>>
java基于Socket设计一个Mail的收发客户端
查看>>
leetcode Missing Number
查看>>
leetcode Ugly Number
查看>>
leetcode Add Digits
查看>>
leetcode Binary Tree Paths
查看>>
leetcode Longest Palindromic Substring
查看>>
leetCode House Robber II
查看>>
leetcode Binary Tree Right Side View
查看>>
leetcode Repeated DNA Sequences
查看>>
leetcode Implement Trie (Prefix Tree)
查看>>
leetcode Add and Search Word - Data structure design
查看>>
leetcode Minimum Size Subarray Sum
查看>>
leetcode Container With Most Water
查看>>