字数:2434,阅读耗时:3 分 40 秒。
老规矩,先上案例代码,这样大家可以更加熟悉是如何使用的,看过 Mybatis 系列的小伙伴,对这段代码差不多都可以背下来了。
哈哈~,有点夸张吗?不夸张的,就这行代码。
public class MybatisApplication {
public static final String URL = "jdbc:mysql://localhost:3306/mblog";
public static final String USER = "root";
public static final String PASSWORD = "123456";
public static void main(String[] args) {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
SqlSession sqlSession = null;
try {
inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
//今天主要这行代码
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.selectById(1));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
sqlSession.close();
}
}
复制代码
看源码有什么用?
图片
通过源码的学习,我们可以收获 Mybatis 的核心思想和框架设计,另外还可以收获设计模式的应用。
前两篇文章我们已经Mybatis配置文件解析到获取SqlSession,下面我们来分析从 SqlSession 到 userMapper:
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
复制代码
前面那篇文章已经知道了这里的 sqlSession 使用的是默认实现类 DefaultSqlSession。所以我们直接进入 DefaultSqlSession 的 getMapper 方法。
//DefaultSqlSession中
private final Configuration configuration;
//type=UserMapper.class
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
复制代码
这里有三个问题:
图片
问题 1:getMapper 返回的是个什么对象?
上面可以看出,getMapper 方法调用的是 Configuration 中的 getMapper 方法。然后我们进入 Configuration 中
//Configuration中
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
////type=UserMapper.class
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
复制代码
这里也没做什么,继续调用 MapperRegistry 中的 getMapper:
//MapperRegistry中
public class MapperRegistry {
//主要是存放配置信息
private final Configuration config;
//MapperProxyFactory 的映射
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
//获得 Mapper Proxy 对象
//type=UserMapper.class,session为当前会话
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//这里是get,那就有add或者put
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);
}
}
//解析配置文件的时候就会调用这个方法,
//type=UserMapper.class
public <T> void addMapper(Class<T> type) {
// 判断 type 必须是接口,也就是说 Mapper 接口。
if (type.isInterface()) {
//已经添加过,则抛出 BindingException 异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//添加到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
//创建 MapperAnnotationBuilder 对象,解析 Mapper 的注解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
//标记加载完成
loadCompleted = true;
} finally {
//若加载未完成,从 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
复制代码
MapperProxyFactory 对象里保存了 mapper 接口的 class 对象,就是一个普通的类,没有什么逻辑。
在 MapperProxyFactory 类中使用了两种设计模式:
单例模式 methodCache(注册式单例模式)。
工厂模式 getMapper()。
继续看 MapperProxyFactory 中的 newInstance 方法。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(SqlSession sqlSession) {
//创建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//最终以JDK动态代理创建对象并返回
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}
复制代码
从代码中可以看出,依然是稳稳的基于 JDK Proxy 实现的,而 InvocationHandler 参数是 MapperProxy 对象。
//UserMapper 的类加载器
//接口是UserMapper
//h是mapperProxy对象
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h){
}
复制代码
问题 2:为什么就可以调用他的方法?
上面调用 newInstance 方法时候创建了 MapperProxy 对象,并且是当做 newProxyInstance 的第三个参数,所以 MapperProxy 类肯定实现了 InvocationHandler。
进入 MapperProxy 类中:
//果然实现了InvocationHandler接口
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;
}
//调用userMapper.selectById()实质上是调用这个invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果是Object的方法toString()、hashCode()等方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
//JDK8以后的接口默认实现方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//创建MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//下一篇再聊
return mapperMethod.execute(sqlSession, args);
}
}
复制代码
也就是说,getMapper 方法返回的是一个 JDK 动态代理对象(类型是 $Proxy+数字)。这个代理对象会继承 Proxy 类,实现被代理的接口 UserMpper,里面持有了一个 MapperProxy 类型的触发管理类。
当我们调用 UserMpper 的方法时候,实质上调用的是 MapperProxy 的 invoke 方法。
图片
为什么要在 MapperRegistry 中保存一个工厂类?
原来他是用来创建并返回代理类的。这里是代理模式的一个非常经典的应用。
MapperProxy 如何实现对接口的代理?
JDK 动态代理
我们知道,JDK 动态代理有三个核心角色:
被代理类必须实现接口,因为要通过接口获取方法,而且代理类也要实现这个接口。
而 Mybatis 中并没有 Mapper 接口的实现类,怎么被代理呢?它忽略了实现类,直接对 Mapper 接口进行代理。
MyBatis 动态代理:
在 Mybatis 中,JDK 动态代理为什么不需要实现类呢?
图片
这里我们的目的其实就是根据一个可以执行的方法,直接找到 Mapper.xml 中 statement ID ,方便调用。
最后返回的 userMapper 就是 MapperProxyFactory 的创建的代理对象,然后这个对象中包含了 MapperProxy 对象,
问题 3:到底是怎么根据 Mapper.java 找到 Mapper.xml 的?
最后我们调用 userMapper.selectUserById(),本质上调用的是 MapperProxy 的 invoke()方法。
请看下面这张图:
图片
如果根据(接口+方法名找到 Statement ID ),这个逻辑在 InvocationHandler 子类(MapperProxy 类)中就可以完成了,其实也就没有必要在用实现类了。
图片
总结
本文中主要是讲 getMapper 方法,该方法实质上是获取一个 JDK 动态代理对象(类型是 Proxy+数字),这个代理类会继承 MapperProxy 类,实现被代理的接口 UserMapper,并且里面持有一个 MapperProxy 类型的触发管理类。这里我们就拿到代理类了,后面我们就可以使用这个代理对象进行方法调用。
问题涉及到的设计模式:
代理模式。
工厂模式。
单例模式。
整个流程图:
图片
冰冻三尺,非一日之寒表面意义是冰冻了三尺,并不是一天的寒冷所能达到的效果。学习亦如此,你每一天的一点点努力,都是为你以后的成功做铺垫。
推荐阅读
面试官:Integer缓存最大范围只能是-128到127吗?
6000多字 | 秒杀系统设计注意点【理论】
面试官:说说你对Java异常的理解
《程序员面试宝典》.pdf下
评论