Mybatis是如何对Mapper接口进行代理的
本篇文章是通过看视频学习总结的内容, 如有错误的地方请谅解,并联系博主及时修改,谢谢您的阅读.
注意:
本篇博客接着前三篇博客,主要是从第一篇博客的测试例子中开始延申,直到到源码的分析。
源码地址: mybatis 中文注释版
前三篇博客地址:
Mybatis(三) - Mybatis是如何通过SqlSessionFactory得到SqlSession的
Mybatis(二) - Mybatis是如何创建出SqlSessionFactory的
Mybatis(一) - Mybatis 最原始是使用方式
前言:在前三篇博客中论述了如何通过配置文件得到SqlSessionFactory
的, 如果通过SqlSessionFactory
得到 SqlSession
,在本文中主要讲解 Mybatis 通过Session
如何得到 Mapper
接口的实例方法,如果对代码存在疑问,请查看 《Mybatis(一) - Mybatis 最原始是使用方式》的 Junit 测试代码, 也可以直接阅读一下代码:
@Test
public void customPagePlugin() {
SqlSession session = this.sqlSessionFactory.openSession();
// 本文要分析的是这句代码
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> page = mapper.getUserPage("10086");
page.stream().forEach(System.out::println);
}
一、源码跟进
- 1.1 进入
session.getMapper(UserMapper.class)
方法,在第二篇文章中分析到了SqlSessionFactory
得到的SqlSession
对象其实就是DefaultSqlSession
对象,那么在这里也只是进入到org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper()
即可。这里可以看到,在DefaultSqlSession
对象中,存在了Configuration
对象,得到Mapper
实例却调用了它里面的方法,然后继续跟进到Configuration.getMapper()
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
- 1.2 进入到
org.apache.ibatis.session.Configuration#getMapper()
方法中,又调用了org.apache.ibatis.binding.MapperRegistry#getMapper()
方法,于是继续跟进:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
- 1.3 进入到
org.apache.ibatis.binding.MapperRegistry#getMapper()
中,很明显第一句话就是通过Mapper(接口)
的Class
对象,在Map
中获取到了一个MapperProxyFactory
类型,这里先跳过MapperProxyFactory
的研究,接下来就是通过的到的MapperProxyFactory
对象,去创建mapper(接口)
的代理对象,那么继续跟进到org.apache.ibatis.binding.MapperProxyFactory#newInstance()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从初始化阶段完成的mapper接口注册中获取到当前接口对应的代理类
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
T t = mapperProxyFactory.newInstance(sqlSession);
return t;
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
- 1.4 进入
org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
方法中,这里就特别明显了,直接new
出一个MapperProxy
对象,传入了sqlSession、 mapperInterface、methodCache
三个参数作为构造器参数。然后调用重载的newInstance
方法,传入MapperProxy
,最终使用JDK
动态代理完成了Mapper(接口)
的代理
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 这个方法是我们关注的
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
二、步骤分析
- 2.1 为什么
DefaultSqlSession
中会保存一份Configuration
对象,在调用getMapper()
的时候,为什么又会到Configuration
中调用MapperRegistry.getMapper()
?
在最开始的加载配置文件的时候,说到这么
Configuration
对象是全局唯一的配置类,是一个单例对象,那么既然是全局的单例配置对象,肯定里面包含了完整的执行一个sql
的所有需要的内容,在文章《Mybatis(三) - Mybatis是如何通过SqlSessionFactory得到SqlSession的》阐述了执行一条sql
所需要的所有内容。初始化阶段创建出SqlSessionFactory
对象时,就已经完成了配置文件的解析,将所有的mapper(接口)
全部初始化到Configuration.mapperRegistor
容器中保存,现在需要使用到Mapper(接口)
代理,那么肯定是从容器中取出能够代理接口创建的工厂对象,拿到代理mapper(接口)
的代理工厂对象,代理mapper(接口)
- 2.2
MapperProxyFactory
是如何创建的?
这个问题需要追溯到初始化阶段,找到
org.apache.ibatis.binding.MapperRegistry#addMapper()
方法,这个方法是在初始化节点,解析mybatis-config.xml
文件中<Mappers>
标签的时候调用的,代码如下:
// 解析 mybatis-config.xml 文件中 <Mappers> 标签,加入到 Configuration.MapperRegistry容器中
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// !Map<Class<?>, MapperProxyFactory<?>> 存放的是接口类型,和对应的工厂类的关系
knownMappers.put(type, new MapperProxyFactory<>(type));
// 注册了接口之后,根据接口,开始解析所有方法上的注解,例如 @Select >>
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
这里
knownMappers.put(type, new MapperProxyFactory<>(type))
中,就可以看到MapperProxyFactory
是通过new
关键字传入Mapper(接口)
作为构造器参数进行创建的,然后给成员变量private final Class<T> mapperInterface;
赋值。所以在最终调用的时候,我只需要通过mapper.class
集合获取到指定对象的 代理工厂。
- 2.3 为什么使用
JDK
动态代理,而不是Cglib
动态代理?
Mybatis 源码中大量使用了
JDK
的动态代理,并没有使用Cglib
。JDK
动态代理最终原理是通过创建一个类,实现接口中所有的方法,从而实现代理。而Cglib
则是通过类来集成原有的类,实现的动态代理,结合两个动态代理到mybatis
环境中,mybatis
设计是通过mapper
接口加上方法名称来锁定一个唯一的sql
语句,那么问题就来了,如果使用类继承(继承谁?),没法继承啊,对吧。
总结:
mybatis
创建接口的代理对象是通过JDK
动态代理实现的,而不是Cglib
mybatis
是通过全局唯一的接口名称加上方法名称锁定一个sql
语句