MyBatis源码解读(三)获取Mapper对象

MyBatis源码解读(三)获取Mapper对象

MyBatis源码解读之获得Mapper对象

DefaultSqISession的selectOne()方法可以直接根据Mapper.xm中的Statement ID执行。但是这种方式属于硬编码,修改起来也很麻烦。

另一个问题是如果参数传入错误,在编译阶段也是不会报错的,不利于预先发现问题。

1
List<User> list = sqlSession.selectList("com.dxysun.mybatis.mapper.UserMapper.selectUserByName", "zhangsan");

在MyBatis后期的版本提供了第二种调用方式,就是定义一个接口,然后再调用Mapper接口的方法。

由于我们的接口名称跟Mapper.xml的namespace是对应的,接口的方法跟statementID也都是对应的,所以根据方法就能找到对应的要执行的SQL。

1
2
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserByName("zhangsan");

这里有两个问题需要解决:

1、getMapper获得的是一个什么对象?为什么可以执行它的方法?

2、到底是怎么根据Mapper找到XML中的SQL执行的?

1、getMapper()

DefaultSqlSession的getMapper()方法,调用了Configuration的getMapper()

1
2
3
4
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}

Configuration的getMapper()方法,又调用了MapperRegistry的getMapper() 方法。

1
2
3
4
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// mapperRegistry中注册的有Mapper的相关信息 在解析映射文件时 调用过addMapper方法
return mapperRegistry.getMapper(type, sqlSession);
}

我们知道,在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到了一个Map中。获取Mapper代理对象,实际上是从Map中获取对应的工厂类后,调用以下方法创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取Mapper接口对应的代理对象
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取Mapper接口对应的 MapperProxyFactory 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

在newlnstance()方法中,先创建MapperProxy。

MapperProxy实现了InvocationHandler接口,主要属性有三个:sqlSession、 mapperlnterface、methodCache。

1
2
3
4
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

最终通过JDK动态代理模式创建、返回代理对象:

1
2
3
4
5
6
7
/**
* 创建实现了 mapperInterface 接口的代理对象
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
// 1:类加载器 2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

也就是说,getMapper()返回的是一个JDK动态代理对象(类型是$Proxy数字)。这个代理对象会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。

MapperRegistry中保存一个工厂类,是用来创建返回代理类的。

这里是代理模式的一个非常经典的应用。

但是为什么要直接代理一个接口呢?

2、MapperProxy如何实现对接口的代理

我们知道,JDK的动态代理,有三个核心角色:被代理类(实现类)、接口、实现了InvocationHandler的触发管理类,用来生成代理对象。

被代理类必须实现接口,因为要通过接口获取方法,而且代理类也要实现这个接口。

image-20210523230004983

而MyBatis里面的Mapper没有实现类,怎么被代理呢?它忽略了实现类,直接对接口进行代理。

MyBatis的动态代理:

image-20210523230121938

在MyBatis里面,动态代理为什么不需要实现类呢?

我们的目的是根据一个可以执行的方法,直接找到Mapper.xml中的StatementID,方便调用。

如果根据接口类型+方法的名称找到Statement ID这个逻辑在Handler类(MapperProxy)中就可以完成,其实也就没有实现类的什么事了。

总结

获得Mapper对象的过程,实质上是获取了一个JDK动态代理对象(类型是$Proxy数字)。

这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。

打赏

请我喝杯咖啡吧~

支付宝
微信