MyBatis中使用动态代理机制分析

概述

​ 近期,对mybatis源码进行了一段粗略的学习,对之前一些不太理解的问题也有了进一步的理解。其中就包括动态代理机制在mybatis中的使用,这篇文章主要对这一点进行分析,以便在日后遗忘的时候可以看一看。

MyBatis中动态代理

​ 我们都知道Spring中AOP机制就是通过java的动态代理实现的,个人理解动态代理就是一种代理模式的实现,只不过动态代理中的代理类是在运行期生成的(也可以通过该点来区分静态代理和动态代理,静态代理是代理类在编译器就生成了)。java通过Proxy类和InvocationHandler接口方便的实现了动态代理。这里我们不进行动态代理分析,先看看mybatis中如何使用了动态代理,先上一段简单的mybatis实现数据库访问的代码,有一个数据表User记录用户的id,userName,userAge三个属性,其中userMapper.xml文件如下:

mapper.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 定义操作user表的sql映射文件userMapper.xml -->
<mapper namespace="com.qiongqiong.bean.UserMapper">
<select id="selectUserByID" parameterType="int" resultType="User">
select * from `user` where id = #{id}
</select>
</mapper>

​ 接口UserMapper定义如下:

1
2
3
4
5
6
7
8
9
import java.util.List;

/**
* Created by yuanqiongqiong on 2018/5/13.
*/
public interface UserMapper {

List<User> selectUserByID(int id);
}

​ 当我们使用调用selectUserByID时会使用如下的方式:

1
2
3
4
5
6
7
8
9
SqlSessionFactory sqlSessionFactory;
Reader reader;
String resource = "configuration.xml";
reader= Resources.getResourceAsReader(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = (User) userMapper.selectUserByID(1);
session.close();

​ 通过这种方式我们就完成了对数据库的访问操作。但是,大家一定会有疑问,我们没有实现UserMapper接口,userMapper.selectUserByID(1)方法是如何执行的。没错,是java的动态代理,mybatis使用了动态代理机制为UserMapper生成了一个代理类,为了清楚的认识到代理类的实现,我将该代理类的字节码打印到文件中,并在IDEA下进行反编译如下。userMapperProxy类实现了代理类和UserMapper接口。

UserMapperImpl

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;

public final class userMapperProxy extends Proxy implements UserMapper {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public userMapperProxy(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final List selectUserByID(int var1) throws {
try {
return (List)super.h.invoke(this, m3, new Object[]{Integer.valueOf(var1)});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.qiongqiong.bean.UserMapper").getMethod("selectUserByID", new Class[]{Integer.TYPE});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

​ 如普通的动态代理一致,当调用selectUserByID方法时会调用InvocationHandler中的invoke方法,去调用相应的实现方法。我们继续看一下userMapperProxy中的InvocationHandler的实现类MapperProxy,从invoke方法代码中我看出如果调用方法是接口中定义的数据库操作方法,那么执行如下逻辑:MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); 看到这两行代码我们终于明白sql的具体执行,最终还是通过sqlSession来执行。MapperMethod封装了xml具体的sql语句,其实这个执行活动类似 User user =(User) session.selectOne(“selectUserByID”, 1); 这条语句。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}

if(this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}

MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if(mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}

return mapperMethod;
}

@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(new Class[]{Class.class, Integer.TYPE});
if(!constructor.isAccessible()) {
constructor.setAccessible(true);
}

Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup)constructor.newInstance(new Object[]{declaringClass, Integer.valueOf(2)})).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}

private boolean isDefaultMethod(Method method) {
return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
}
}
袁琼琼 wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
多谢支持,共同成长!
0%